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関数があります。

今まで見てきた上記のような基本のパターンに対して、initupdateにいくつか変更が加えられ、また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を使ってつくるプログラムでは、システムは以下のような構成となります:

initupdate関数からコマンドを発行することができるようになりました。これによりHTTPリクエストの送信といったようなことをいつでもできます。また、なにか意味ある情報を待ち受けすることもできるようになりました(後ほどの節でサブスクリプションに関する例をあつかいます!)。

results matching ""

    No results matching ""