Elm Architectureの応用 ⑵:非同期通信の導入
リッチなWebアプリケーションの構築に非同期通信は欠かせません。 Elm Architectureに非同期通信を組み込むにはどうすればよいでしょうか?
Taskの利用
ElmにはTaskという非同期処理を行うための仕組みが用意されています。
次の例は、非同期通信でテキストを取得する例です。 いくつか新しい文法が登場しますが、1つずつ見ていきますので焦る必要はありません。
import Http import Json.Decode port getData : Task Http.Error String port getData = Http.getString "https://example.com/"
Http.getString
関数はURLを渡すと、Task Http.Error String
型のTaskを返します。 Task Http.Error String
という型は、成功時にString
型、失敗時にHttp.Error
型の結果を返すTaskを表しています。
Taskはその挙動をすることを宣言しているだけで、それ自体では動作しません。 Elmでは、手続き型言語のように、外界に作用する処理をコード中で動作させることができないのです。 そこで、Taskを実際に動作させるためにportという文法を使用します。 portはElmプログラムが外界(JavaScript)とやりとりするための文法で、Task以外にも様々な値をやり取りできます。 ここでは、TaskをJavaScriptの世界に送り出すことで、定義された処理を初期化時に実行しています。
コールバックを扱う
さて、これで非同期通信を走らせることができますが、まだ結果を使っていないので意味がありません。 Elm Architectureに乗るために、何らかのActionを定義してAddressに送りつけてやるのがよいでしょう。
import Http getData : Task Http.Error String getData = Http.getString "https://example.com/" port task : Task () () port task = getData `andThen` (\data -> Signal.send address (Init data)) -- 成功時 `onError` (\err -> Signal.send address (Error err)) -- 失敗時
ここでしていることは、getData
で得られた結果をInit data
というActionの形でAddressに送りつけています。 また、エラーの場合は同様にError err
というActionにしてAddressに送りつけています。
ちなみに、ここで登場しているandThen
とonError
は特別なキーワードではなく普通の関数です。 関数や型の詳細については、後述のコラムを参照してください。
サンプル:初期データを非同期で取得する
次に示すのは、初期データを非同期で取得する例です。
import Html exposing (Html, text) import Http import Task exposing (Task, andThen, onError) actions : Signal.Mailbox Action actions = Signal.mailbox NoOp state : Signal Model state = Signal.foldp update init actions.signal main : Signal Html main = Signal.map (view actions.address) state getData : Task Http.Error String getData = Http.getString "https://example.com/" -- データを取得 port task : Task () () port task = getData `andThen` (\result -> Signal.send actions.address (Init result)) -- 成功時 `onError` (\err -> Signal.send actions.address (Error (toString err))) -- 失敗時 -- MODEL type alias Model = String init : Model init = "now loading" -- UPDATE type Action = NoOp | Init String | Error String update : Action -> Model -> Model update action model = case action of NoOp -> model Init result -> result -- 成功時 Error e -> "error: " ++ e -- 失敗時 -- VIEW view : Signal.Address Action -> Model -> Html view address model = text model
なお、このコードはTry Elmでも実行することはできますが、クロスドメイン制約(同一生成元ポリシー)違反でエラーになってしまいます。 成功パターンを試すためには、例えばlocalhostなら同じlocalhostからデータを取得してください。
andThenとonError
Taskの実行結果をコールバック関数で処理するためには、andThen
やonError
を使います。
getData : Task Http.Error String getData = Http.getString "https://example.com/" port task : Task x () port task = getData `andThen` (\data -> Signal.send address (Init data)) `onError` (\err -> Signal.send address (Error err))
andThen
やonError
は、特別なキーワードではなく普通の関数です。 andThen
に付いているバッククォートは、引数の順番を変えて読みやすくするための仕掛けです。 意味は次のように書いているのと同じです。
port task = andThen getData (\data -> Signal.send address (Init data))
andThen
の定義は次のようになっています。
andThen : Task x a -> (a -> Task x b) -> Task x b
Taskの処理結果(a
)を受け取って新しいTaskを生成するコールバックを渡すことにより、次々とTaskを連鎖できるようになっています。
一方、Signal.sendはAddressに対してデータを送りつける関数です。 Signal.send
の定義は次のようになっています。
send : Address a -> a -> Task x ()
Address宛てにデータを送りつけるという挙動も1つのTaskです。成功時の型は()
で、フィードバックがないことを意味しています。
エラー処理はonError
を使います。使い方はandThen
と同様で、エラーを受け取って別のタスクに変換します。
port task : Task () () port task = getData `andThen` (\data -> Signal.send actions.address (Init data)) `onError` (\e -> Signal.send actions.address (Error e))
なお、Taskを扱う関数は、ここで紹介した他にもたくさんあります。 興味のある方はこちらを参照してください。