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

[サンプルコード](https://gist.github.com/miyamoen/5337337a27710d742d96f95bcbd7bcc4)

# まとめ

```Status.elm
type Status a
    = NotAsked
    | Loading
    | Failure Http.Error
    | Success a
```
こういう型を作っておくといい感じにAPIから取ってくる値を扱えて便利です


# はじめに

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

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

# 型

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

- [カスタム型 -- 型の設計](https://guide.elm-lang.jp/types/custom_types.html)
- [krisajenkins/remotedata](https://package.elm-lang.org/packages/krisajenkins/remotedata/latest/)

elm guideを引用します

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

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

## 自分だけの型を設計する

package化された[krisajenkins/remotedata](https://package.elm-lang.org/packages/krisajenkins/remotedata/latest/)もありますが結局自分で作った方がいいと思います
エラーをどう扱うかって段階的に変わっていきますから

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

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

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

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

### `Result Http.Error a`から変換する

[elm/httpのexpect系の関数](https://package.elm-lang.org/packages/elm/http/latest/Http#expectJson)をみてみましょう

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

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

```elm
type Msg = GotItem (Result Http.Error Item)
```

これを

```elm
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`になって前取ってきた値がなくなってしまいますね
問題ないってときもあれば、新しい値をとってこれるまでは昔の値を表示しておきたいときもあると思います

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

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

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

## 使い方

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

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

２つのAPIがあるとします

```elm
type alias Book = {..}

type alias Books = List Book

type alias Model = { books : WithStatus Books }
```

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

```elm
-- 複数冊取得
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

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

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

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