-
Notifications
You must be signed in to change notification settings - Fork 7
Description
Describe the bug
When returning cmds and cause new subscriptions, it is not quite defined in which order the cmds and subscriptions are processed.
To Reproduce
Example can be found here: https://ellie-app.com/wX8PpqvLsH8a1
Steps to reproduce the issue (please include code snippets, input files, or commands if possible):
- Open the console and press +1. You should see
msg: GotIt "from JS". - Then flip the commented out code in
init. Suddenly you won’t see that log when pressing +1 anymore.
That’s because the init change causes the ports to be registered in the opposite order in the compiled JS, and that determines whether the port subscription is processed before the port command.
Expected behavior
Port messages should be delivered consistently, regardless of unrelated changes to the program structure or where port commands are called.
Actual behavior / error output
After adding or moving port calls (for example, inside init), Elm sometimes fails to receive messages from JavaScript, even though the ports are correctly defined and connected.
Example Code or Project
Elm (Main.elm):
port module Main exposing (main)
import Browser
import Html exposing (Html, button, div, p, text)
import Html.Events exposing (onClick)
port getIt : String -> Cmd msg
port gotIt : (String -> msg) -> Sub msg
type alias Model =
{ count : Int }
init : () -> ( Model, Cmd Msg )
init () =
( { count = 0 }
-- Flip to the `getIt` line here to trigger the bug.
--, Cmd.none
, getIt "via init"
-- In this case, calling `getIt` here is useless.
-- But imagine adding another `getIt` call in a bigger app
-- and suddenly the original use breaks. Oops!
)
type Msg
= Increment
| Decrement
| GotIt String
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case Debug.log "msg" msg of
Increment ->
( { model | count = model.count + 1 }, getIt "via update Increment" )
Decrement ->
( { model | count = model.count - 1 }, Cmd.none )
GotIt _ ->
( model, Cmd.none )
subscriptions : Model -> Sub Msg
subscriptions model =
if model.count > 0 then
gotIt GotIt
else
Sub.none
view : Model -> Html Msg
view model =
div []
[ p [] [ text """Open the console and press +1. You should see `msg: GotIt "from JS"`.""" ]
, p [] [ text """Then flip the commented out code in `init`. Suddenly you won’t see that log when pressing +1 anymore.""" ]
, p [] [ text """That’s because the `init` change causes the ports to be registered in the opposite order in the compiled JS, and that determines whether the port subscription is processed before the port command.""" ]
, button [ onClick Increment ] [ text "+1" ]
, div [] [ text <| String.fromInt model.count ]
, button [ onClick Decrement ] [ text "-1" ]
]
main : Program () Model Msg
main =
Browser.element
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
}
HTML (index.html):
<html>
<head>
</head>
<body>
<main></main>
<script>
var app = Elm.Main.init({ node: document.querySelector('main') })
app.ports.getIt.subscribe(s => {
console.log("app.ports.getIt", s);
app.ports.gotIt.send("from JS");
});
</script>
</body>
</html>
Additional context
Reported here:
- https://discord.com/channels/534524278847045633/731183487775932478/1432444010160722063
- Subscribing to a port can suddenly stop working due to generated code order elm/core#1154
A proposed fixed was done here: https://discord.com/channels/534524278847045633/731183487775932478/1432860816423518308
@@ -1938,11 +1938,15 @@ function _Platform_setupEffects(managers, sendToApp)
{
var ports;
+ // sort the entries in _Platform_effectManagers to ensure consistent order
+ var valFun = (m) => m.a ? m.e ? 2 : 3 : 1;
+ var sorted = Object.entries(_Platform_effectManagers).sort(
+ ([, m1], [, m2]) => valFun(m1) - valFun(m2)
+ );
+
// setup all necessary effect managers
- for (var key in _Platform_effectManagers)
+ for (var [key, manager] of sorted)
{
- var manager = _Platform_effectManagers[key];
-
if (manager.a)
{
ports = ports || {};