我们继续谈论榆木0.18 。
榆树 舒适尴尬
榆树 舒适而笨拙。 组成
榆树 舒适而笨拙。 Json.Encoder和Json.Decoder
在本文中,我们考虑与服务器端交互的问题。
查询执行
简单查询的示例可以在Http包的描述中找到。
请求类型为Http.Request a 。
查询结果类型为Result Http.Error a。
两种类型均由用户类型参数化,在生成请求时必须指定其解码器。
您可以使用以下功能执行请求:
- Http.send;
- Http.toTask。
Http.send允许您执行请求,并在完成后将消息传递给第一个参数中指定的更新函数。 该消息携带有关请求结果的数据。
Http.toTask允许您从可以执行的请求中创建任务 。 我认为,使用Http.toTask函数是最方便的,因为可以使用各种功能 (例如Task.map2)组合Task实例。
让我们来看一个例子。 例如,要保存用户数据,必须执行两个连续的从属查询。 让它从用户创建帖子并将照片保存到他(使用了一些CDN)。
首先,考虑Http.Send案例的实现。 为此,我们需要两个功能:
save : UserData -> Request Http.Error UserData save userData = Http.post “/some/url” (Http.jsonBody (encodeUserData userData)) decodeUserData saveImages : Int -> Images -> Request Http.Error CDNData saveImages id images = Http.post (“/some/cdn/for/” ++ (toString id)) (imagesBody images) decodedCDNData
将不描述UserData和CDNData类型;例如,它们并不重要。 函数encodeUserData是一个编码器。 saveImages接受用于形成地址的用户数据标识符和照片列表。 imagesBody函数形成multipart / form-data类型的请求主体。 函数decodeUserData和decodedCDNData分别解码服务器对用户数据的响应和CDN请求的结果。
接下来,我们需要两条消息,即查询结果:
type Msg = DataSaved (Result Http.Error UserData) | ImagesSaved (Result Http.Error CDNData)
假设在执行更新功能的某处,有一段代码存储数据。 例如,它可能看起来像这样:
update : Msg -> Model -> (Model, Cmd Msg) update msg model case Msg of ClickedSomething -> (model, Http.send DataSaved (save model.userData))
在这种情况下,将创建查询并用DataSaved消息标记。 此外,收到此消息:
update : Msg -> Model -> (Model, Cmd Msg) update msg model case Msg of DataSaved (Ok userData) -> ( {model | userData = userData}, Http.send ImagesSaved (saveImages userData.id model.images)) DataSaved (Err reason) -> (model, Cmd.None)
如果成功保存,我们将更新模型中的数据并调用请求以将照片保存到我们转移接收到的用户数据标识符的位置。 处理ImagesSaved消息将与DataSaved相似,因此有必要处理成功和失败的案例。
现在考虑使用Http.toTask函数的选项。 使用描述的函数,我们定义一个新函数:
saveAll : UserData -> Images -> Task Http.Error (UserData, CDNData) saveAll : userData images = save model.userData |> Http.toTask |> Task.andThen (\newUserData -> saveImages usersData.id images |> Http.toTask |> Task.map (\newImages -> (userData, newImages) } )
现在,使用合并任务的功能,我们可以在一条消息中获得所有数据:
type Msg = Saved (Result Http.Error (UserData, CDNData)) update : Msg -> Model -> (Model, Cmd Msg) update msg model case Msg of ClickedSomething -> (model, Task.attempt Saved (saveAll model.userData model.images)) DataSaved (Ok (userData, images)) -> ( {model | userData = userData, images = images}, Cmd.none) DataSaved (Err reason) -> (model, Cmd.None)
为了执行请求,我们使用Task.attempt函数,该函数使您可以完成任务。 不要与Task.perform函数混淆。 Task.perform-允许您完成不会失败的任务。 Task.attempt-执行可能失败的任务。
就消息数量,更新功能的复杂性而言,此方法更为紧凑,并允许您将逻辑保持在本地。
在我的项目,应用程序和组件中,我经常创建Commands.elm模块,在其中描述与服务器部分进行交互的功能,其类型为...-> Task Http.Error a。
请求执行状态
在执行查询的过程中,通常必须完全或部分地阻塞该接口,并且还必须报告查询执行中的错误(如果有)。 通常,请求的状态可以描述为:
- 请求失败
- 请求正在进行中;
- 该请求已成功完成。
- 请求失败。
对于这种描述,有一个RemoteData包。 最初,我积极使用它,但是随着时间的流逝,其他类型的WebData的存在变得多余了,使用它很乏味。 代替此软件包,出现了以下规则:
- 将服务器中的所有数据声明为Maybe。 在这种情况下,Nothing表示没有数据;
- 在应用程序或组件模型中声明Int类型的加载属性。 该参数存储已执行请求的数量。 这种方法的唯一不便之处是需要分别在请求开始时和完成时增加和减少属性;
- 在应用程序或组件模型中声明List String类型的错误属性。 此属性用于存储错误数据。
实践证明,所描述的方案并不比RemoteData包中的选项好得多。 如果有人有其他选择,请分享评论。
请求执行状态应包括从Http.Progress包中的下载进度。
任务顺序
考虑开发中经常发现的任务序列的选项:
- 顺序相关任务;
- 一致的独立任务;
- 并行的独立任务。
上面已经考虑了相继的相关任务;在本节中,我将给出一般的描述和实现方法。
第一次失败时,任务序列将中断,并返回错误。 如果成功,则返回结果的某种组合:
someTaskA |> Task.andThen (\resultA -> someTaskB |> Task.map (\resultB -> (resultA, resultB) ) )
此代码创建任务错误(a,b)类型的任务,稍后可以执行。
如果前一个任务成功完成,则Task.andThen函数可让您转移新任务以执行。 Task.map函数允许您在成功的情况下转换执行的甜瓜结果。
有一些选项无法成功完成任务,因此有必要检查数据的一致性。 假设用户ID匹配:
someTaskA |> Task.andThen (\resultA -> someTaskB |> Task.andThen (\resultB -> case resultA.userId == resultB.userId of True -> Task.succeed (resultA, resultB) False -> Task.fail “User is not the same” ) )
值得注意的是,使用Task.andThen函数代替Task.map函数,并使用Task.succeed和Task.fail函数独立确定第二个任务的成功。
如果其中一项任务可能失败并且可以接受,那么在出现错误的情况下,您需要使用Task.onError函数指示该值:
someTaskA |> Task.onError (\msg -> Task,succeed defaultValue) |> Task.andThen (\resultA -> someTaskB |> Task.map (\resultB -> (resultA, resultB) ) )
声明任务后,应立即声明对Task.onError函数的调用。
可以使用Task.mapN函数执行连续的独立查询。 这使您可以将多个任务结果合并为一个。 第一个掉落的任务会中断整个链的执行,因此请使用Task.onError函数作为默认值。 还要检查Task.sequence函数,它使您可以执行一系列类似的任务。
当前语言实现中的并行任务未描述。 通过更新功能中的事件处理,可以在应用程序或组件级别实现它们。 所有逻辑都保留在开发人员的肩膀上。