失敗なんか気にせずとにかくPromise.all()でぜんぶやる

# こんなことがあった

1. あるAPIにアクセスすると本日執筆された記事IDの一覧が帰ってくる
1. その記事IDを使って記事の内容を取得できるAPIにアクセスして、記事を取得したい

# とりあえず実装する

*前提: 以下で登場するインスタンス、メソッドには架空のものがあります。このようなことがしたいのだろう。程度に解釈してください。*

何も考えず実装するとこのようになった。

```typescript
const fetchEntryIds = (): Promise<Array<string>> => {
  return request.get('xxxxx/entries/new');
};

const fetchEntry = (entryId: string): Promise<Entry> => {
  return request.get(`xxxxx/entries/${entryId}`);
};

const fetchTodaysEntries = async (): Promise<Array<Entry>> => {
  const entryIds: Array<string> = await fetchEntryIds();

  return Promise.all<Entry>(entryIds.map<Promise<Entry>>((entryId: string) => {
    return fetchEntry(entryId);
  }));
};
```

# 期待したこと

`Promise.all()`は`Array<Promise<?>>`を`Promise<Array<?>>`にしてくれる便利なメソッドであり、内部の`Promise<?>`の動作を並列に行ってくれる。  
-> 記事をまとめて取得できるのでは!?

# 👶今回起きたこと

1. **当たり前だが**APIリクエストは失敗することがある
1. `Promise.all()`は内包するすべてのPromiseがfulfilledにならないとfulfilledにならない

そのためリクエストがひとつでもrejectedになってしまうと`Promise.all()`はrejectedになり、その値が返却される。**悲しいことに**それまでいくつかfulfilledになっているPromiseがあったとしても**なかったことになる**。

## 図解

以下の記号を使います。

+ pending = 👶
+ fulfilled = 🤖
+ rejected = 🧟

以下は左辺にこれらの絵文字を配列状に筆記したもので、右辺はそれを`Promise.all()`で実行した場合の結果です。

```
[👶, 👶, 👶, 👶, 👶] => 👶
[👶, 👶, 🤖, 👶, 👶] => 👶
[👶, 🤖, 🤖, 👶, 🤖] => 👶
[🤖, 🤖, 🤖, 🤖, 🤖] => 🤖
[👶, 👶, 👶, 🧟, 👶] => 🧟
[👶, 🧟, 👶, 🧟, 👶] => 🧟
[🧟, 👶, 👶, 🤖, 👶] => 🧟
[🧟, 🤖, 🤖, 🤖, 🤖] => 🧟
```

# 今回やりたいこと

失敗でいいから`Promise.all()`の動作を遮らずにすべて実行してほしい。

# "実装": "0.1.0"

今回の実装は外部パッケージを一切使わずにできました。
あとで見返せば当たり前じゃないかって思いますが、思いついたときは若干感動しました。

```typescript
const fetchTodaysEntries = async (): Promise<Array<void | Entry>> => {
  const entryIds: Array<string> = await fetchEntryIds();

  return Promise.all<void | Entry>(entryIds.map<Promise<void | Entry>>((entryId: string) => {
    return fetchEntry(entryId).catch(() => {});
  }));
};
```

## こつ

一度rejectedになったPromiseをfulfilledに戻します。そのために`promise.catch()`で例外を捕捉し、その捕捉にしかるべき処理を施します。**今回は握り潰しましたごめん**。

## 問題点

`promise.catch()`の戻り値が記事のクラス`Entry`ではないため`Promise.all()`の戻り値のジェネリクスが`<Entry>`から`<void | Entry>`に変わりました。`fetchTodaysEntries()`を使う人はその戻り値について`undefined`かどうかの判定が必要です。

他にNull Object patternや番兵を使って動作を制御させる方法もありますが、以下はそれよりもより良いだろう方法の紹介です。

# "実装": "1.0.0"

`Discriminated unions`を使います。

## `Discriminated unions`

複数の型が同名のプロパティを持ち、そのプロパティの型が違うことがわかっていれば、それらから構成される`Union type`はそのプロパティの型が何かを判定できればTypeScriptは`Union type`がどの型であるかを確定できます。

## よくある例

人気No.1モナドの`Optional<T>`を部分的に実装する例が最も有名です。

```typescript
type Some<T> = {
  present: true;
  value: T;
};

type None = {
  present: false;
};

type Optional<T> = Some<T> | None;
```

この`Optional<T>`において**複数の型が同名のプロパティを持ち、そのプロパティ型が違うことがわかっていれば**とは`present`です。`Some<T>, None`は`present`において異なる`Literal type`を持っているので(`boolean`ではなく`true, false`であることに注目してください)この値の判定で変数が`Some<T>`か`None`のどちらかであることを確定できます。

```typescript
if (optional.present) {
  // TypeScriptはoptional = Some<T>と解釈している
  optional.value // 🤗
}
else {
  // TypeScriptはoptional = Noneと解釈しているため以下はできない
  optional.value // 🙄
}
```

## 今回用意した`Discriminated unions`

今回は`Try<T>`のモナドを模しました。

```typescript
type Success<T> = {
  success: true;
  value: T;
};

type Failure = {
  success: false;
  err: unknown;
};

type Try<T> = Success<T> | Failure;
```

あとはこれに沿うように成功時は`Success<T>`、例外発生時は`Failure`のオブジェクトリテラルで包みます。

これが`1.0.0`です。

```typescript
const fetchTodaysEntries = async (): Promise<Array<Try<Entry>>> => {
  const entryIds: Array<string> = await fetchEntryIds();

  return Promise.all<Try<Entry>>(entryIds.map<Promise<Try<Entry>>>((entryId: string) => {
    return fetchEntry(entryId).then((entry: Entry) => {
      return {
        success: true,
        value: entry
      };
    }, (err: unknown) => {
      return {
        success: false,
        err
      };
    });
  }));
};
```

### `promise.then()`

使用例が珍しいですが`promise.then()`は第2引数を受けることができます。この第2引数のコールバックは例外発生時のことで、じつは`promise.catch()`の第1引数と同じです。そのため正常時と例外発生時を`promise.then()`ひとつで処理できます。

もちろん`promise.catch()`を使う方法も可能ですが、オブジェクト生成コストが内部的には少し増えます。**気持ちの問題**程度の差だとは思うので読みやすさを考慮して適宜選択してください。

```typescript
const fetchTodaysEntries = async (): Promise<Array<Try<Entry>>> => {
  const entryIds: Array<string> = await fetchEntryIds();

  return Promise.all<Try<Entry>>(entryIds.map<Promise<Try<Entry>>>((entryId: string) => {
    return fetchEntry(entryId).then((entry: Entry) => {
      return {
        success: true,
        value: entry
      };
    }).catch((err: unknown) => {
      return {
        success: false,
        err
      };
    });
  }));
};
```

### 👶async/awaitでやりたい！

できます。手を加えなければいけないところが少々変わります。

#### `fetchEntry()`

```typescript
const fetchEntry = async (entryId: string): Promise<Try<Entry>> => {
  try {
    const entry: Entry = await request.get(`xxxxx/entries/${entryId}`);

    return {
      success: true,
      value: entry
    };
  }
  catch (err: unknown) {
    return {
      success: false,
      err
    };
  }
};

```

## `array.filter()`できれいにできる

`Type predicate`を戻り値に持つ`array.filter()`がTypeScriptのArrayにはoverloadで定義されているので`Success<T>`だけ、`Failure`だけが必要ならそれを使います。

```typescript
const extractSuccess = (arr: Array<Try<Entry>>): Array<Success<Entry>> => {
  return arr.filter((t: Try<Entry>): t is Success<Entry> => {
    return t.success;
  });
};

const extractFailure = (arr: Array<Try<Entry>>): Array<Failure> => {
  return arr.filter((t: Try<Entry>): t is Failure => {
    return !t.success;
  });
};
```