Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid Http Race Condition in Elm

Tags:

elm

Let's assume we have a text input field and on every change of its content we send an Http request to a search API. Now, we don't have any guarantee that the Http responses get back to elm in the same order that we sent the requests.

What's the easiest way to make sure we react to the response corresponding to the latest request – rather than the latest response, which might correspond to an outdated search string? Is there an easy way to attach the query string to the message returned by Elm's http effect? Or any other way we can link the response to the request by which it was triggered?

I'd like to avoid including the query in the response of the search API if possible. Another remedy would be to debounce the search, but that would just decrease the probability of using the wrong response, whereas we'd like to eliminate it.

Thanks for your help!

Example:

import Html
import Html exposing (..)
import Html.Events exposing (onClick, onInput)
import Http
import Json.Decode as Decode


main = Html.program
        { init = ( { searchText = "", result = "" }, Cmd.none )
        , update = update
        , subscriptions = (\model -> Sub.none)
        , view = view
        }


type alias Model =
    { searchText : String
    , result: SearchResult  
    }


type alias SearchResult = String


type Msg 
    = NewSearchText String
    | ReceivedResponse (Result Http.Error SearchResult)


update msg model = 
    case msg of 

        NewSearchText newText ->
            ( { model | searchText = newText}
            , getSearchResult newText
            )

        ReceivedResponse (Result.Ok response) ->
            ( { model | result = response }
            , Cmd.none
            ) 

        ReceivedResponse (Result.Err error) ->
            Debug.crash <| (toString error)



getSearchResult : String -> Cmd Msg
getSearchResult query =
    let 
        url = "http://thebackend.com/search?query=" ++ query

        request : Http.Request SearchResult  
        request = Http.get url Decode.string
    in
        Http.send ReceivedResponse request            


view model =
    div [] 
        [ Html.input [onInput (\text -> NewSearchText text)] []
        , Html.text model.result
        ]
like image 652
egdvnyjklu Avatar asked Jul 20 '26 02:07

egdvnyjklu


1 Answers

Yes, it is possible to attach the query string to the response. First, augment your message type to handle the additional data:

type Msg 
  = NewSearchText String
  | ReceivedResponse String (Result Http.Error SearchResult)

Then, change your Http.send call to attach the query text to the ReceivedResponse message:

Http.send (ReceivedResponse query) request

Finally, in your update, grab the query in your pattern match on the resulting Msg:

case msg of
  ReceivedResponse query (Ok response) ->
    ...
  ReceivedResponse query (Err err) ->
    ...

Why does this work?

The Http.send function's first argument can be an arbitrary function that consumes a Result Http.Error SearchResult and turns it into a Msg. In your original code, that function is just ReceivedResponse, the Msg constructor. When the Msg type is updated so that ReceivedResponse takes two arguments, the ReceivedResponse constructor function becomes a curried two-argument function, and ReceivedResponse "some query here" is a one-argument function that takes in a Result and returns a Msg.

like image 76
Alex Lew Avatar answered Jul 22 '26 16:07

Alex Lew