【JavaScript】Promise実践パターン

## はじめに
`JavaScript`・`TypeScript`にちゃんと触るようになって約半年。その過程で扱った非同期処理のパターンをまとめていきたいと思います。
中級者向けの内容だと思いますので~~偉そうなこと言ってお前は中級者なのか~~、基礎から固めたいという方は手前味噌ですが[こちら](https://qiita.com/al_tarte/items/4af009c9b21c13d5a368)をどうぞ。

## こんな人向け
- `Promise`・`await`辺りの仕組みがなんとなく分かってきた
- 非同期処理のネストが深くなってしまっている
- `firestore`や`axios`を使い始めた・使いたい

## 本編
### 1. 非同期in非同期

これはホントよく出会うパターン。非同期処理の結果を別の非同期関数に渡してその結果を・・・(ry
非同期が入れ子になっていってネスト地獄になってしまいます。

```微妙.js
// 非同期関数1
const p1 = async() => {
  return 1;
};

// 非同期関数2
const p2 = async(index) => {
  const l = ['A', 'B'];
  return l[index];
};

// 非同期関数1で取得した値で非同期関数2を実行する
const main = async() => {
  p1().then(index => {
    p2(index).then(result => {
      console.log('result', result);
    })
  });
};

main();

// "result" "B"
```

頭のいい人は大丈夫でも、自分みたいなスペックだとだんだん処理が追えなくなってきます。
ので、以下のようにしてみます。

```マシ.js
// 非同期関数1
const p1 = async() => {
  return 1;
};

// 非同期関数2
const p2 = async(index) => {
  const l = ['A', 'B'];
  return l[index];
};

// 非同期関数1で取得した値で非同期関数2を実行する
const main = async() => {
  const index = await p1();
  const result = await p2(index);
  console.log('result', result);
};

main();
```

あ＾〜、うざったいネストが消えてスッキリしました。
脳死で書いているとついついネストにしてしまいがちですが、意識して平らになるようにするといいと思います。

### 2. エラー値も同時に取得したいパターン

そうは言ってもエラーの値も返したい時どうすんだよ。

```微妙.js
// 非同期関数1
const p1 = async() => {
  return 2;
};

// 非同期関数2
const p2 = async(index) => {
  const l = ['A', 'B'];
  const r = l[index];
  if (!r) {
    throw('Error: index out of range.');
  }
  return r;
};

// 非同期関数1で取得した値で非同期関数2を実行する
const main = async() => {
  const index = await p1();
  let result;
  await p2(index)
    .then(r => {
      result = r;
    })
    .catch(e => {
      result = e;
    });
  console.log('result', result);
};

main();

// "result" "Error: index out of range."
```

でたよ`let`。コイツの悪いところは、どこで値が変わるかわかりづらいこと。できれば使いたくない。どら○も〜ん、なんか出してよ〜。

```マシ.js
// 非同期関数1
const p1 = async() => {
  return 2;
};

// 非同期関数2
const p2 = async(index) => {
  const l = ['A', 'B'];
  const r = l[index];
  if (!r) {
    throw('Error: index out of range.');
  }
  return r;
};

// 非同期関数1で取得した値で非同期関数2を実行する
const main = async() => {
  const index = await p1();
  const result = await p2(index).catch(e => e); // return e; と同義
  console.log('result', result);
};

main();

// "result" "Error: index out of range."
```

の○太くん、そういう時はね、`catch()`の中で`return`しちゃえばいいのさ。
これで`let`とはサヨナラバイバイです。

### 3. then()の中で処理した結果を返したい
```js
// 非同期関数1
const p1 = async() => {
  return 1;
};

// 非同期関数2
const p2 = async(index) => {
  const l = ['A', 'B'];
  const r = l[index];
  if (!r) {
    throw('Error: index out of range.');
  }
  return r;
};

// 非同期関数1で取得した値で非同期関数2を実行する
const main = async() => {
  const index = await p1();
  const isB = await p2(index).then(r => r === 'B');
  console.log('result', isB);
};

main();

// "result" true
```

非同期処理の結果からなんらかの処理をした値が実際に欲しい値、というパターンでは有用です。
これならぶっちゃけ処理の中身を見なくても「結果がBかどうか」が返ってくると分かるので個人的に好きです。

### 4. rejectバケツリレー

非同期処理の結果を処理して返す関数から、最初の非同期処理のエラーをそのまま引き渡したい時があります。

```js
// 非同期関数1
const p1 = async(index) => {
  const l = ['A', 'B'];
  const r = l[index];
  if (!r) {
    throw('Error: index out of range.'); // 非同期関数1 -> 非同期関数2
  }
  return r;
};

// 非同期関数2
const p2 = async() => {
  const result = await p1(2).catch(e => {
    throw(e); // 非同期関数1 -> 非同期関数2 -> main
  });
  const res = 'result is ' + result;
  return res;
};

// 非同期関数1で取得したエラーを非同期関数2から返す
const main = async() => {
  const result = await p2().catch(e => {
    console.log('Error', e);
  });
  console.log('result', result);
};

main();

// "Error" "Error: index out of range."
// "result" undefined
```

これも単純な話で`throw()`を使ってエラーをそのまま渡しちゃえばOKです。

### 5. 非同期処理の結果で非同期処理ループ

これもめっちゃよく見るパターン。`.forEach()`内では`await`が使えないので`for-of`を使って・・・こんな感じ？

```微妙.js
// 非同期関数1
const p1 = async(index) => {
  return [0, 1];
};

// 非同期関数2
const p2 = async(index) => {
  const l = ['A', 'B'];
  const r = l[index];
  if (!r) {
    throw('Error: index out of range.');
  }
  return r;
};

// 非同期関数1で取得した値から非同期ループ
const main = async() => {
  const indexList = await p1();
  const results = [];
  for (const index of indexList) {
    results.push(await p2(index));
  }
  console.log("results", results);
};

main();

// "results" ["A", "B"]
```

やりたいことはできていますけれど、「非同期処理」の強みをお忘れではありませんこと？そう、「並列化」ですわ！

```マシですわ.js
// 非同期関数1
const p1 = async(index) => {
  return [0, 1];
};

// 非同期関数2
const p2 = async(index) => {
  const l = ['A', 'B'];
  const r = l[index];
  if (!r) {
    throw('Error: index out of range.');
  }
  return r;
};

// 非同期関数1で取得した値から非同期ループ
const main = async() => {
  const indexList = await p1();
  const tasks = indexList.map(index => p2(index));
  const results = await Promise.all(tasks);
  console.log('results', results);
};

main();

// "results" ["A", "B"]
```

`Promise.all()`は渡した非同期処理を並列で実行して、**全て**が「満足」状態(resolve)になることで「満足」状態になる非同期関数です。

### 6. エラーを想定した並列処理

上で`Promise.all()`を使いましたが、コイツにも弱点があります。それは、**一つでも**「拒絶」状態（reject）になると「拒絶」状態になってしまうことです。まずは上の例で意図的にエラーを起こしてみます。

```ﾀﾋんだんじゃないの〜☆.js
// 非同期関数1
const p1 = async(index) => {
  return [0, 1, 2];
};

// 非同期関数2
const p2 = async(index) => {
  const l = ['A', 'B'];
  const r = l[index];
  if (!r) {
    throw('Error: index out of range.');
  }
  return r;
};

// 非同期関数1で取得した値から非同期ループ
const main = async() => {
  const indexList = await p1();
  const tasks = indexList.map(index => p2(index));
  console.log('開始');
  const results = await Promise.all(tasks);
  console.log('終了');
  console.log(results);
};

main();

// "開始"
```

`Promise.all()`、完全に沈黙。しかも`await`で待っているので次の処理にすら進めません。
こんなことになりかねないので、扱いには注意しましょう。

エラーが想定される場合は`Promise.allSettled()`を使います。

```js
// 非同期関数1
const p1 = async(index) => {
  return [0, 1, 2];
};

// 非同期関数2
const p2 = async(index) => {
  const l = ['A', 'B'];
  const r = l[index];
  if (!r) {
    throw('Error: index out of range.');
  }
  return r;
};

// 非同期関数1で取得した値から非同期ループ
const main = async() => {
  const indexList = await p1();
  const tasks = indexList.map(index => p2(index));
  const results = await Promise.allSettled(tasks);
  console.log(results);
};

main();

/*
[
  Object {
    status: "fulfilled",
    value: "A"
  },
  Object {
    status: "fulfilled",
    value: "B"
  },
  Object {
    status: "rejected",
    reason: "Error: index out of range."
  },
]
*/
```

`Promise.allSettled()`はこんな感じで結果を返します。`status`は「満足」or「拒絶」、`value`は「満足」結果、`reason`はもちろん「拒絶」理由、といった内訳です。
まぁ、「成功したやつだけ欲しい」なら以下のようにすればいいでしょうか。

```成功したやつだけ欲しい.js
const results = (await Promise.allSettled(tasks))
  .filter(r => r.status === 'fulfilled')
  .map(r => r.value);
console.log(results);
```

### 7. 一つでも成功したら満足

このパターンにはあまり出会ったことがありませんが、流れで紹介します。

```js
// 非同期関数1
const p1 = async(index) => {
  return [2, 1, 0]; // reject, resolve, resolve
};

// 非同期関数2
const p2 = async(index) => {
  const l = ['A', 'B'];
  const r = l[index];
  if (!r) {
    throw('Error: index out of range.');
  }
  return r;
};

// 非同期関数1で取得した値から非同期ループ
const main = async() => {
  const indexList = await p1();
  const tasks = indexList.map(index => p2(index));
  const result = await Promise.any(tasks);
  console.log('result', result);
};

main();

// "result" "B"
```

`Promise.any()`は一度でも「満足」状態になったら、その段階で「満足」状態になります。
上の例だと、2つ目で成功してBが返ってきていますね。

## まとめ
- なるべく「平ら」なコードにしましょう
- ~~`const`こそ絶対神（異論は認める）~~
- 非同期関数の「拒絶」はとりま`throw()`で
- `then()`・`catch()`内からのreturnを有効活用しましょう
- 非同期と並列化は使いよう

最後まで読んでいただきありがとうございました！