避免 Elm 中的 Http 竞争条件

Avoid Http Race Condition in Elm

假设我们有一个文本输入字段,每次更改其内容时,我们都会向搜索发送一个 Http 请求 API。现在,我们无法保证 Http 响应会按照我们发送请求的相同顺序返回到 elm。

确保我们对对应于最新请求而不是最新响应的响应做出反应的最简单方法是什么?可能对应于过时的搜索字符串?有没有一种简单的方法可以将查询字符串附加到 Elm 的 http 效果返回的消息中?或者我们可以通过任何其他方式 link 对触发它的请求的响应?

如果可能,我想避免在搜索响应中包含查询 API。另一种补救方法是去抖动搜索,但这只会降低使用错误响应的可能性,而我们想消除它。

感谢您的帮助!

示例:

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
        ]

是的,可以将查询字符串附加到响应中。首先,扩充消息类型以处理额外数据:

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

然后,更改您的 Http.send 调用以将查询文本附加到 ReceivedResponse 消息:

Http.send (ReceivedResponse query) request

最后,在您的 update 中,在结果 Msg:

的模式匹配中获取查询
case msg of
  ReceivedResponse query (Ok response) ->
    ...
  ReceivedResponse query (Err err) ->
    ...

为什么这样做有效?

Http.send 函数的第一个参数可以是使用 Result Http.Error SearchResult 并将其转换为 Msg 的任意函数。在您的原始代码中,该函数只是 ReceivedResponse,即 Msg 构造函数。当更新 Msg 类型以便 ReceivedResponse 接受 两个 个参数时,ReceivedResponse 构造函数变成一个柯里化的双参数函数,而 ReceivedResponse "some query here" 是接受 Result 和 returns 一个 Msg.

的单参数函数

这是一种方法:

向您的模型添加两个整数:

  1. requestsSent : Int -- 发出的请求数。
  2. lastReceived : Int -- 您处理的最新请求。

修改 ReceivedResponse 以将 Int 作为第一个值:

| ReceivedResponse Int (Result Http.Error SearchResult)

现在,无论何时发出请求,都会在模型中将 requestsSent 递增 1,并通过部分应用 ReceivedResponse:

来递增 "tag" 请求
Http.send (ReceivedResponse model.requestsSent) request

在您的 update 函数中,检查 ReceivedResponse 中的 Int 是否大于 lastReceived。如果是,则处理它,并将 lastReceived 的值设置为该响应的 Int。如果不是,请丢弃它,因为您已经处理了一个较新的请求。