Nous continuons de parler de l' orme 0,18 .
Orme. Confortable et maladroit
Orme. Confortable et maladroit. La composition
Orme. Confortable et maladroit. Json.Encoder et Json.Decoder
Dans cet article, nous examinons les problÚmes d'interaction avec le cÎté serveur.
ExĂ©cution de requĂȘte
Des exemples de requĂȘtes simples peuvent ĂȘtre trouvĂ©s dans la description du package Http .
Le type de demande est Http.Request a .
Le type de rĂ©sultat de la requĂȘte est Result Http.Error a.
Les deux types sont paramĂ©trĂ©s par le type d'utilisateur, dont le dĂ©codeur doit ĂȘtre spĂ©cifiĂ© lors de la gĂ©nĂ©ration de la requĂȘte.
Vous pouvez exécuter la demande à l'aide des fonctions:
- Http.send;
- Http.toTask.
Http.send vous permet d'exécuter la demande et à la fin, transmet le message à la fonction de mise à jour spécifiée dans le premier argument. Le message contient des données sur le résultat de la demande.
Http.toTask vous permet de crĂ©er une tĂąche Ă partir d'une demande que vous pouvez exĂ©cuter. Ă mon avis, l'utilisation de la fonction Http.toTask est la plus pratique, car les instances de tĂąche peuvent ĂȘtre combinĂ©es Ă l' aide de diverses fonctions , par exemple Task.map2 .
Regardons un exemple. Par exemple, pour enregistrer les donnĂ©es utilisateur, deux requĂȘtes dĂ©pendantes consĂ©cutives doivent ĂȘtre exĂ©cutĂ©es. Que ce soit en crĂ©ant un message de l'utilisateur et en lui enregistrant des photos (certains CDN sont utilisĂ©s).
Tout d'abord, considérez l'implémentation du cas Http.Send. Pour cela, nous avons besoin de deux fonctions:
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
Les types UserData et CDNData ne seront pas dĂ©crits; par exemple, ils ne sont pas importants. La fonction encodeUserData est un encodeur. saveImages accepte un identifiant de donnĂ©es utilisateur, qui est utilisĂ© pour former l'adresse, et une liste de photos. La fonction imagesBody forme un corps de requĂȘte de type multipart / form-data . Les fonctions decodeUserData et decodedCDNData dĂ©codent la rĂ©ponse du serveur pour les donnĂ©es utilisateur et le rĂ©sultat de la demande au CDN, respectivement.
Ensuite, nous avons besoin de deux messages, les rĂ©sultats de la requĂȘte:
type Msg = DataSaved (Result Http.Error UserData) | ImagesSaved (Result Http.Error CDNData)
Supposons que, quelque part dans l'implémentation de la fonction de mise à jour, il existe une section de code qui stocke les données. Par exemple, cela pourrait ressembler à ceci:
update : Msg -> Model -> (Model, Cmd Msg) update msg model case Msg of ClickedSomething -> (model, Http.send DataSaved (save model.userData))
Dans ce cas, une requĂȘte est créée et marquĂ©e avec un message DataSaved. De plus, ce message est reçu:
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)
En cas de sauvegarde rĂ©ussie, nous mettons Ă jour les donnĂ©es du modĂšle et appelons la demande de sauvegarde des photos oĂč nous transfĂ©rons l'identifiant de donnĂ©es utilisateur reçu. Le traitement du message ImagesSaved sera similaire Ă DataSaved, il sera nĂ©cessaire de gĂ©rer les cas rĂ©ussis et ayant Ă©chouĂ©.
Considérez maintenant l'option d'utiliser la fonction Http.toTask. En utilisant les fonctions décrites, nous définissons une nouvelle fonction:
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) } )
Maintenant, en utilisant la possibilité de combiner des tùches, nous pouvons obtenir toutes les données dans un seul message:
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)
Pour exĂ©cuter les requĂȘtes, nous utilisons la fonction Task.attempt , qui vous permet de terminer la tĂąche. Ă ne pas confondre avec la fonction Task.perform . Task.perform - vous permet d'effectuer des tĂąches qui ne peuvent pas Ă©chouer . Task.attempt - Effectue des tĂąches qui peuvent Ă©chouer .
Cette approche est plus compacte en termes de nombre de messages, de complexité de la fonction de mise à jour et permet de garder la logique plus locale.
Dans mes projets, applications et composants, je crée souvent le module Commands.elm, dans lequel je décris les fonctions d'interaction avec la partie serveur avec le type ... -> Task Http.Error a.
Demander le statut d'exécution
Dans le processus d'exĂ©cution des requĂȘtes, l'interface doit souvent ĂȘtre bloquĂ©e en tout ou en partie, et Ă©galement signaler les erreurs d'exĂ©cution des requĂȘtes, le cas Ă©chĂ©ant. En gĂ©nĂ©ral, l'Ă©tat d'une demande peut ĂȘtre dĂ©crit comme:
- la demande n'est pas terminée;
- la demande est en cours;
- La demande a été acceptée;
- la demande a échoué.
Pour une telle description, il existe un package RemoteData . Au début, je l'ai utilisé activement, mais au fil du temps, la présence d'un type supplémentaire de WebData est devenue redondante, et travailler avec elle était fastidieux. Au lieu de ce package, les rÚgles suivantes sont apparues:
- dĂ©clarer toutes les donnĂ©es du serveur comme Peut-ĂȘtre. Dans ce cas, Nothing, indique l'absence de donnĂ©es;
- dĂ©clarer un attribut de chargement de type Int dans le modĂšle d'application ou de composant. Le paramĂštre stocke le nombre de requĂȘtes exĂ©cutĂ©es. Le seul inconvĂ©nient de cette approche est la nĂ©cessitĂ© d'incrĂ©menter et de dĂ©crĂ©menter l'attribut au dĂ©but de la demande et Ă la fin, respectivement;
- déclarer un attribut d'erreur de type List String dans le modÚle d'application ou de composant. Cet attribut est utilisé pour stocker des données d'erreur.
Le schéma décrit n'est pas beaucoup mieux que l'option avec le package RemoteData, comme le montre la pratique. Si quelqu'un a d'autres options, partagez les commentaires.
Le statut d'exécution de la demande doit inclure la progression du téléchargement à partir du package Http.Progress .
Séquence de tùches
Considérez les options pour les séquences de tùches qui se trouvent souvent dans le développement:
- tùches dépendantes séquentielles;
- tùches indépendantes cohérentes;
- tùches indépendantes parallÚles.
Les tĂąches dĂ©pendantes successives ont dĂ©jĂ Ă©tĂ© examinĂ©es ci-dessus; je vais donner une description gĂ©nĂ©rale et des approches de mise en Ćuvre dans cette section.
La séquence de tùches est interrompue lors du premier échec et une erreur est renvoyée. En cas de succÚs, une combinaison de résultats est renvoyée:
someTaskA |> Task.andThen (\resultA -> someTaskB |> Task.map (\resultB -> (resultA, resultB) ) )
Ce code crĂ©e une tĂąche de type Erreur de tĂąche (a, b), qui peut ĂȘtre effectuĂ©e ultĂ©rieurement.
La fonction Task.andThen vous permet de transférer une nouvelle tùche pour exécution si la précédente est terminée avec succÚs. La fonction Task.map vous permet de convertir les résultats d'exécution du melon en cas de succÚs.
Il existe des options lorsque la réussite de la tùche ne sera pas suffisante et que vous devez vérifier la cohérence des données. Supposons que les ID utilisateur correspondent:
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â ) )
Il convient de noter qu'au lieu de la fonction Task.map, la fonction Task.andThen est utilisée et le succÚs de la deuxiÚme tùche est déterminé indépendamment à l'aide des fonctions Task.succeed et Task.fail .
Si l'une des tùches peut échouer et que cela est acceptable, vous devez utiliser la fonction Task.onError pour indiquer la valeur en cas d'erreur:
someTaskA |> Task.onError (\msg -> Task,succeed defaultValue) |> Task.andThen (\resultA -> someTaskB |> Task.map (\resultB -> (resultA, resultB) ) )
L'appel Ă la fonction Task.onError doit ĂȘtre dĂ©clarĂ© immĂ©diatement aprĂšs la dĂ©claration de la tĂąche.
Des requĂȘtes indĂ©pendantes successives peuvent ĂȘtre effectuĂ©es Ă l'aide des fonctions Task.mapN. Ce qui vous permet de combiner plusieurs rĂ©sultats de tĂąche en un seul. La premiĂšre tĂąche interrompue interrompt l'exĂ©cution de la chaĂźne entiĂšre, utilisez donc la fonction Task.onError pour les valeurs par dĂ©faut. Consultez Ă©galement la fonction Task.sequence , elle vous permet d'effectuer une sĂ©rie de tĂąches similaires.
Les tùches parallÚles dans l'implémentation actuelle du langage ne sont pas décrites. Leur implémentation est possible au niveau de l'application ou du composant grùce au traitement des événements dans la fonction de mise à jour. Toute logique reste sur les épaules du développeur.