taskelmelm-architecture

How to wait for the first arrived result on the thenable chain?


We can use Promise.race to wait for the first arrived result on the thenable chain. The Task module doesn't seem to support it yet, Task.sequence is only the equivalent to Promise.all.

Non-thenable Solution demo:

import Process
import Task


init () =
    ( Nothing, Cmd.batch [ after 2 "2nd", after 1 "1st" ] )


after seconds name =
    Process.sleep (1000 * seconds)
        |> Task.map (always name)
        |> Task.perform Done


type Msg
    = Done String


update (Done name) model =
    case model of
        Nothing ->
            ( Debug.log name <| Just name, Cmd.none )

        _ ->
            ( Debug.log name model, Cmd.none )


main =  
    Platform.worker
        { init = init
        , update = update
        , subscriptions = always Sub.none
        }

Run it, output as expected:

1st: Just "1st"
2nd: Just "1st"

Solution

  • Promise.race as a self-contained function requires maintaining local state to track whether or not it's been resolved already, which as you probably know isn't possible in Elm.

    But you can accomplish the same thing relatively easily by tracking the state in the model yourself. Here's an example using Maybe to track whether we've received a response:

    type Thing =
        ...
    
    getThings : String -> Task Never (List Thing)
    getThings url =
        ...
    
    
    type alias Model =
        { things : Maybe (List Thing) }
    
    type Msg
        = GotThings (List Thing)
    
    
    init =
        ( { things = Nothing }
        , Cmd.batch 
              [ Task.perform GotThings (getThings "https://a-server.com/things")
              , Task.perform GotThings (getThings "https://a-different-server.com/things")
              ]
        )
    
    
    update msg model =
        case msg of
            GotThings things ->
                case model.things of
                    Nothing ->
                        ( { things = Just things }, Cmd.none )
    
                    Just _ ->
                        -- if we have already received the things, ignore any subsequent requests
                        ( model, Cmd.none )
    
    
    view model =
        ...