Wir sprechen weiterhin über Elm 0.18 .
Ulme. Bequem und umständlich
Ulme. Bequem und umständlich. Zusammensetzung
Ulme. Bequem und umständlich. Json.Encoder und Json.Decoder
In diesem Artikel werden Probleme der Interaktion mit der Serverseite behandelt.
Abfrageausführung
Beispiele für einfache Abfragen finden Sie in der Beschreibung des HTTP- Pakets.
Der Anforderungstyp ist Http.Request a .
Der Abfrageergebnistyp ist Ergebnis Http.Error a.
Beide Typen werden durch den Benutzertyp parametrisiert, dessen Decoder beim Generieren der Anforderung angegeben werden muss.
Sie können die Anforderung mit den folgenden Funktionen ausführen:
- Http.send;
- Http.toTask.
Mit Http.send können Sie die Anforderung ausführen und die Nachricht nach Abschluss an die im ersten Argument angegebene Aktualisierungsfunktion übergeben. Die Nachricht enthält Daten über das Ergebnis der Anforderung.
Mit Http.toTask können Sie eine Aufgabe aus einer Anforderung erstellen, die Sie ausführen können. Die Verwendung der Funktion Http.toTask ist meiner Meinung nach am bequemsten, da Task-Instanzen mit verschiedenen Funktionen kombiniert werden können , z. B. Task.map2 .
Schauen wir uns ein Beispiel an. Um beispielsweise Benutzerdaten zu speichern, müssen zwei aufeinanderfolgende abhängige Abfragen ausgeführt werden. Lassen Sie es einen Beitrag vom Benutzer erstellen und Fotos für ihn speichern (einige CDN wird verwendet).
Betrachten Sie zunächst die Implementierung für den Fall Http.Send. Dafür benötigen wir zwei Funktionen:
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
Die UserData- und CDNData-Typen werden nicht beschrieben, beispielsweise sind sie nicht wichtig. Die Funktion encodeUserData ist ein Encoder. saveImages akzeptiert eine Benutzerdaten-ID, die zur Bildung der Adresse verwendet wird, und eine Liste von Fotos. Die imagesBody-Funktion bildet einen Anforderungshauptteil vom mehrteiligen / Formulardatentyp . Die Funktionen decodeUserData und decodedCDNData decodieren die Serverantwort für Benutzerdaten bzw. das Ergebnis der CDN-Anforderung.
Als nächstes benötigen wir zwei Nachrichten, die Abfrageergebnisse:
type Msg = DataSaved (Result Http.Error UserData) | ImagesSaved (Result Http.Error CDNData)
Angenommen, irgendwo in der Implementierung der Aktualisierungsfunktion gibt es einen Codeabschnitt, in dem Daten gespeichert werden. Zum Beispiel könnte es so aussehen:
update : Msg -> Model -> (Model, Cmd Msg) update msg model case Msg of ClickedSomething -> (model, Http.send DataSaved (save model.userData))
In diesem Fall wird eine Abfrage erstellt und mit einer DataSaved-Nachricht markiert. Ferner wird diese Nachricht empfangen:
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)
Bei erfolgreichem Speichern aktualisieren wir die Daten im Modell und rufen die Anforderung zum Speichern der Fotos auf, auf die wir die empfangene Benutzerdaten-ID übertragen. Die Verarbeitung der ImagesSaved-Nachricht ähnelt der von DataSaved. Es ist erforderlich, erfolgreiche und fehlgeschlagene Fälle zu behandeln.
Betrachten Sie nun die Option, die Funktion Http.toTask zu verwenden. Mit den beschriebenen Funktionen definieren wir eine neue Funktion:
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) } )
Durch die Möglichkeit, Aufgaben zu kombinieren, können wir jetzt alle Daten in einer Nachricht abrufen:
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)
Zum Ausführen von Anforderungen verwenden wir die Funktion Task.attempt , mit der Sie die Aufgabe ausführen können. Nicht zu verwechseln mit der Funktion Task.perform . Task.perform - Ermöglicht das Ausführen von Aufgaben, die nicht fehlschlagen können . Task.attempt - Führt Aufgaben aus, die möglicherweise fehlschlagen .
Dieser Ansatz ist kompakter in Bezug auf die Anzahl der Nachrichten, die Komplexität der Aktualisierungsfunktion und ermöglicht es Ihnen, die Logik lokaler zu halten.
In meinen Projekten, Anwendungen und Komponenten erstelle ich häufig das Modul Commands.elm, in dem ich die Funktionen der Interaktion mit dem Serverteil mit dem Typ ... -> Task Http.Error a beschreibe.
Ausführungsstatus anfordern
Bei der Ausführung von Abfragen muss die Schnittstelle häufig ganz oder teilweise blockiert werden und gegebenenfalls auch Fehler bei der Ausführung von Abfragen melden. Im Allgemeinen kann der Status einer Anfrage wie folgt beschrieben werden:
- die Anfrage wurde nicht abgeschlossen;
- die Anfrage ist in Bearbeitung;
- Die Anfrage wurde erfolgreich abgeschlossen.
- Anfrage fehlgeschlagen.
Für eine solche Beschreibung gibt es ein RemoteData- Paket. Zuerst habe ich es aktiv genutzt, aber im Laufe der Zeit wurde das Vorhandensein eines zusätzlichen WebData- Typs überflüssig und die Arbeit damit war mühsam. Anstelle dieses Pakets wurden die folgenden Regeln angezeigt:
- Deklarieren Sie alle Daten vom Server als Vielleicht. In diesem Fall zeigt Nothing das Fehlen von Daten an.
- Deklarieren Sie ein Ladeattribut vom Typ Int im Anwendungs- oder Komponentenmodell. Der Parameter speichert die Anzahl der ausgeführten Anforderungen. Die einzige Unannehmlichkeit dieses Ansatzes besteht in der Notwendigkeit, das Attribut zu Beginn der Anforderung bzw. nach Abschluss zu erhöhen und zu verringern.
- Deklarieren Sie ein Fehlerattribut vom Typ List String im Anwendungs- oder Komponentenmodell. Dieses Attribut wird zum Speichern von Fehlerdaten verwendet.
Das beschriebene Schema ist nicht viel besser als die Option mit dem RemoteData-Paket, wie die Praxis zeigt. Wenn jemand andere Optionen hat, teilen Sie in den Kommentaren.
Der Ausführungsstatus der Anforderung sollte den Download-Fortschritt aus dem Http.Progress- Paket enthalten.
Tasksequenz
Berücksichtigen Sie die Optionen für Tasksequenzen, die häufig in der Entwicklung zu finden sind:
- sequentiell abhängige Aufgaben;
- konsistente unabhängige Aufgaben;
- parallele unabhängige Aufgaben.
Aufeinanderfolgende abhängige Aufgaben wurden bereits oben betrachtet, und ich werde in diesem Abschnitt eine allgemeine Beschreibung und Ansätze zur Implementierung geben.
Die Tasksequenz wird beim ersten Fehler unterbrochen und ein Fehler zurückgegeben. Bei Erfolg wird eine Kombination von Ergebnissen zurückgegeben:
someTaskA |> Task.andThen (\resultA -> someTaskB |> Task.map (\resultB -> (resultA, resultB) ) )
Dieser Code erstellt eine Aufgabe vom Typ Aufgabenfehler (a, b), die später ausgeführt werden kann.
Mit der Funktion Task.andThen können Sie eine neue Aufgabe zur Ausführung übertragen, wenn die vorherige erfolgreich abgeschlossen wurde. Mit der Funktion Task.map können Sie Melon-Ausführungsergebnisse bei Erfolg konvertieren.
Es gibt Optionen, bei denen der erfolgreiche Abschluss der Aufgabe nicht ausreicht und Sie die Konsistenz der Daten überprüfen müssen. Angenommen, die Benutzer-IDs stimmen überein:
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” ) )
Es ist anzumerken, dass anstelle der Funktion Task.map die Funktion Task.andThen verwendet wird und der Erfolg der zweiten Aufgabe unabhängig mithilfe der Funktionen Task.succeed und Task.fail bestimmt wird .
Wenn eine der Aufgaben fehlschlagen kann und dies akzeptabel ist, müssen Sie die Funktion Task.onError verwenden, um den Wert im Fehlerfall anzugeben:
someTaskA |> Task.onError (\msg -> Task,succeed defaultValue) |> Task.andThen (\resultA -> someTaskB |> Task.map (\resultB -> (resultA, resultB) ) )
Der Aufruf der Funktion Task.onError sollte unmittelbar nach der Deklaration der Task deklariert werden.
Aufeinanderfolgende unabhängige Abfragen können mit den Funktionen Task.mapN ausgeführt werden. So können Sie mehrere Aufgabenergebnisse zu einem kombinieren. Die erste heruntergefallene Aufgabe unterbricht die Ausführung der gesamten Kette. Verwenden Sie daher die Funktion Task.onError für die Standardwerte. Schauen Sie sich auch die Funktion Task.sequence an , mit der Sie eine Reihe ähnlicher Aufgaben ausführen können.
Parallele Aufgaben in der aktuellen Sprachimplementierung werden nicht beschrieben. Ihre Implementierung ist auf Anwendungs- oder Komponentenebene durch Ereignisverarbeitung in der Aktualisierungsfunktion möglich. Alle Logik bleibt auf den Schultern des Entwicklers.