LoginSignup
9
4

More than 3 years have passed since last update.

[Elm]Httpのエラーハンドリングをしよう

Last updated at Posted at 2019-06-03

サンプルコード

まとめ

Status.elm
type Status a
    = NotAsked
    | Loading
    | Failure Http.Error
    | Success a

こういう型を作っておくといい感じにAPIから取ってくる値を扱えて便利です

はじめに

ElmのHttpはエラーハンドリングをさぼれないのが、いきなりやるのは難しいですよね(もちろんエラーハンドリングはさぼれるんですがどこでどうやろうって困惑すると思います)
JavaScriptだったら正常系をとりあえず書いておけばいいですからね

今回はエラーを含めた状態を型で表してこの問題に対処したいと思います

良いこのみんなはelm guideやpackageサイトをちゃんとよく読んでると思うので知ってると思うのですがネタ元は以下です

elm guideを引用します

type Profile
  = Failure
  | Loading
  | Success { name : String, description : String }

Profile のデータの状態をLoadingから始めて、フェッチに失敗したらFailure、成功したらSuccessというように何が起こったかに応じて状態を遷移させることができます。このような設計はview関数を書くのを本当にシンプルにします。データの状態がカスタム型で表現されているのでview関数はデータをロードしているときも常に妥当な見た目を表示することができます。

もう何もいうことはないです(ちなみにここの翻訳はわたしです)

自分だけの型を設計する

package化されたkrisajenkins/remotedataもありますが結局自分で作った方がいいと思います
エラーをどう扱うかって段階的に変わっていきますから

わたしが今触ってるコードでは以下のように認証エラーだけ特別扱いしています。リトライ処理なんかに使われます
Error型を工夫すれば汎用型でも処理できる範囲なんですが、型はできるだけ平たいほうが扱いやすいです。型がネストしてると面倒です

type Status a
    = NotAsked
    | Loading
    | Failure Http.Error
    | AuthFailure
    | Success a

型はできるだけ平たいほうが扱いやすいといいつつHttp.Errorがあるということは握りつぶしていることを示しています。ご容赦ください

Statusという命名はネタ元を覚えてないです。RemoteDataでもなんでもいいです

Result Http.Error aから変換する

elm/httpのexpect系の関数をみてみましょう

expectJson : (Result Error a -> msg) -> Decoder a -> Expect msg

Result Error a -> msgここの部分は以下のようなMsgに直接食べさせてるんじゃないかと思います

type Msg = GotItem (Result Http.Error Item)

これを

type Msg = GotItem (Status Item)

-- Status.elm
fromHttpResult : Result Http.Error a -> Status a
fromHttpResult result =
    case result of
        Ok data ->
            Success data

        Err err ->
            Failure err

こうします
fromHttpResultをexpectで使ってupdateにはStatus aが来るだけにします。(見た目だけ)Resultがなくなってすっきりしましたね!

あとはこのStatus aの値をModelにいれて、viewで表示するだけです

まとめ1

Status.elm
type Status a
    = NotAsked
    | Loading
    | Failure Http.Error
    | Success a

APIの状態を含めて型に落とし込むと便利

データをキャッシュしたい

ところでこのStatus型は新しくデータを取りに行くとLoadingになって前取ってきた値がなくなってしまいますね
問題ないってときもあれば、新しい値をとってこれるまでは昔の値を表示しておきたいときもあると思います

type alias WithStatus a =
    { status : Status ()
    , data : a
    }

ということでこういう型を使ってます

a型は大体ListやDictになります。

使い方

例えば本の情報を取得するサービスに

  • IDを指定して一冊本の情報を取得
  • ジャンルを指定して複数冊本の情報を取得

2つのAPIがあるとします

type alias Book = {..}

type alias Books = List Book

type alias Model = { books : WithStatus Books }

こんな感じでModelにいれてます

-- 複数冊取得
GotBooks status ->
    { model | books = Status.updateWithStatus Books.update status model.books }

-- 1冊取得
GotBook status ->
    { model | books = Status.updateWithStatus Books.updateByBook status model.books }

-- Books.elm
update : Books -> Books -> Books
update new old =
    ...

updateByBook : Book -> Books -> Books
updateByBook new old =
    ...

こんな感じで更新します
あとはviewでStatusを表示しつつ保持しているコレクションも表示すればいいです

まとめ2

わたしが使っている型についての話でした

type alias WithStatus a =
    { status : Status ()
    , data : a
    }

WithStatus型でAPIから取得する値をうまいこと扱おう

9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4