HTTP
しばしば、インターネット上のどこかにある情報を取得して自分のアプリケーションに表示したいこともあるでしょう。
例えば、1992年に発行されたWalter Lippmann著のPublic Opinion(この本はマスメディアの興りとそれが民主主義に与えた影響についての歴史的な視点を与えてくれます)の全文を取り込みたいとします。この節ではelm/httpパッケージを使い、この本の内容をプログラムに取り込む方法を中心に見ていきます。
青い "Edit" ボタンをクリックしてオンラインエディターでこのプログラムを見てみましょう。Public Opinion の全文が完全に読み込まれるまで画面には "Loading..." と表示されます。今すぐ青いボタンをクリック!
import Browser
import Html exposing (Html, text, pre)
import Http
-- MAIN
main =
Browser.element
{ init = init
, update = update
, subscriptions = subscriptions
, view = view
}
-- MODEL
type Model
= Failure
| Loading
| Success String
init : () -> (Model, Cmd Msg)
init _ =
( Loading
, Http.get
{ url = "https://elm-lang.org/assets/public-opinion.txt"
, expect = Http.expectString GotText
}
)
-- UPDATE
type Msg
= GotText (Result Http.Error String)
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
GotText result ->
case result of
Ok fullText ->
(Success fullText, Cmd.none)
Err _ ->
(Failure, Cmd.none)
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- VIEW
view : Model -> Html Msg
view model =
case model of
Failure ->
text "I was unable to load your book."
Loading ->
text "Loading..."
Success fullText ->
pre [] [ text fullText ]
これまでにThe Elm Architectureの例を見てきましたので、このコードのある程度については馴染み深いはずです。これまでと同じくアプリケーションのModel
、メッセージに応答するためのupdate
関数、そして全てを画面に描画するためのview
関数があります。
今まで見てきた上記のような基本のパターンに対して、init
とupdate
にいくつか変更が加えられ、またsubscription
が追加されています。
init
このinit
関数にはプログラムをどのように初期化するかを記述します:
init : () -> (Model, Cmd Msg)
init _ =
( Loading
, Http.get
{ url = "https://elm-lang.org/assets/public-opinion.txt"
, expect = Http.expectString GotText
}
)
これまでどおりにModel
の初期値を返す必要がありますが、ここではただちに実行したいなんらかのコマンドも同時に返しています。ここで返しているコマンドは最終的にはupdate
関数に渡されるMsg
を返します。
この本の内容を表示するウェブサイトは読み込み中(Loading
)の状態からはじまり、その本の全文を取得(GET)したいとします。Http.get
によってGETリクエストを構築する際に、取得したい本のデータがあるurl
と、どんなデータになることを期待(expect
)するかを指定します。今回のケースでは、指定したurl
は Elm のウェブサイト上のとあるデータを指し示していて、そのデータが画面に表示できる長い文字列(String
)であることを期待(expect
)しています。
ただし、この Http.expectString GotText
の行は単にここでは文字列(String
)を期待している(expect
)と言っているだけではありません。なにかレスポンスを受け取った時に、以下のような GotText
というメッセージに変換されるはずだとも言っているのです。
type Msg
= GotText (Result Http.Error String)
-- GotText (Ok "The Project Gutenberg EBook of ...")
-- GotText (Err Http.NetworkError)
-- GotText (Err (Http.BadStatus 404))
いくつか前の節であつかったResult
型を使っていますね?これによりupdate
関数の中でありうる全ての失敗を完全に捉えることが可能となります。これはupdate
関数の話のついでの余談ですが...
Note: ここでなぜ
init
が関数であるのか(そしてなぜその引数を無視するのか)という事に疑問を抱いたかもしれません。それについては後ほどの JavaScriptとの相互運用 の章にて説明します!(予告: この引数により初期化時にJSから情報を受け取ることができます)
update
今回の例ではupdate
関数もinit
のようにもう少し追加の情報を返します:
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
GotText result ->
case result of
Ok fullText ->
(Success fullText, Cmd.none)
Err _ ->
(Failure, Cmd.none)
この型注釈に注目すると、単に更新されたモデルを返しているだけではなく、Elmに実行してほしいことを指示するためのコマンド も あわせて返していることがわかります。
その実装ではこれまでと同じくメッセージに対してのパターンマッチを行っています。メッセージがGotText
の場合に、HTTPリクエストの結果を示すResult
型の値を調べ、その結果が成功か失敗かによってモデルを更新しています。新しい部分はコマンドも返していることです。
つまり全文の取得に成功した場合には、これ以上は何もしなくて良いことを示すCmd.none
を返しています。すでに全文を取得済みなのですから!
一方、なんらかのエラーがあった場合には、やはりCmd.none
を返してここでは単純にギブアップしています。その結果、画面上に本の内容は表示されません。もしより良い物にしたいのならHttp.Error
に対してパターンマッチを行い、タイムアウトなどのエラーであればリクエストの送信を再度トライすることも可能です。
このupdate
関数で大事なことは、どのようにモデルの更新をするかを決定するだけでなく、新しいコマンドを発行することもできるということです。もっとデータが必要だ!とか乱数がほしい!などと。
subscription
このプログラムにおけるもう一つの新しい部分はsubscription
関数になります。Model
の情報から判断して何らかの情報に対する待ち受けするかどうかを決めることができます。今回の例ではSub.none
として何も待ち受けする必要がないことを示していますが、後ほど現在時刻を待ち受ける必要がある時計の例を見ていきます。
サマリー
Browser.element
を使ってつくるプログラムでは、システムは以下のような構成となります:
init
とupdate
関数からコマンドを発行することができるようになりました。これによりHTTPリクエストの送信といったようなことをいつでもできます。また、なにか意味ある情報を待ち受けすることもできるようになりました(後ほどの節でサブスクリプションに関する例をあつかいます!)。