Reputation: 313
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
]
Upvotes: 7
Views: 250
Reputation: 222198
Here's one way:
Add two integers to your model:
requestsSent : Int
-- the number of requests made.lastReceived : Int
-- the latest request that you've processed.Modify ReceivedResponse
to have an Int as the first value:
| ReceivedResponse Int (Result Http.Error SearchResult)
Now, whenever you make a request, increment requestsSent
by 1 in the model and "tag" the request by partially applying ReceivedResponse
:
Http.send (ReceivedResponse model.requestsSent) request
In your update
function, check if the Int
in the ReceivedResponse
is greater than lastReceived
or not. If it is, process it, and set the value of lastReceived
to this response's Int. If it isn't, discard it, because you've already processed a newer request.
Upvotes: 7
Reputation: 2124
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
.
Upvotes: 7