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を扱う関数は、ここで紹介した他にもたくさんあります。 興味のある方はこちらを参照してください。
