【JavaScript】Promiseの配列を並列実行/逐次実行するには？

# `Promise`の静的メソッド

プロミスの並列実行には、`Promise`クラスの静的メソッドが使えます。
これには`all`, `allSettled`, `any`, `race`があり、以下の共通点があります。

- 引数: [反復可能オブジェクト](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Iteration_protocols#反復可能プロトコル)（`Array`など）
    - 要素に`Promise`があったら、実行が終わるまで待機する
    - 要素にはプロミス以外のものを入れてもOK
- 戻り値: 実行したいプロミスに影響されたプロミス

それぞれのメソッドでは、返されたプロミスの**履行と拒否のタイミング**が違います。
表にまとめるとこのようになります。

| メソッド名 | 履行タイミング | 拒否タイミング |
|:--|:--|:--|
| [`all`](#promiseall) | **全て**のプロミスが履行されたとき | **いずれかの**プロミスが拒否されたとき
| [`allSettled`](#promiseallsettled) | **全て**のプロミスが**決定**されたとき | なし
| [`any`](#promiseany) | **いずれか**のプロミスが履行されたとき | **全て**のプロミスが拒否されたとき
| [`race`](#promiserace) | **いずれか**のプロミスが履行されたとき | **いずれかの**プロミスが拒否されたとき

ここからは各メソッドの解説です。

## `Promise.all`
- 履行: **全て**のプロミスが履行されたとき 
- 拒否: **いずれかの**プロミスが拒否されたとき

個人的に一番よく使うメソッドです。
どれかのプロミスが拒否されたらすぐに検知できるので、ひとまずこれを使っておけばOKな印象があります。

例えば、以下のように一定時間待つと履行されるプロミスの配列があります。

```js
const promises = [
  new Promise(resolve => setTimeout(() => resolve("First"), 1000)),
  new Promise(resolve => setTimeout(() => resolve("Second"), 2000)),
  new Promise(resolve => setTimeout(() => resolve("Third"), 3000))
];
```

これを並列実行し、履行された結果の配列を取得できます。
このとき実行時間は、**一番時間がかかる**最後のプロミスの実行時間となります。

```js
const array = await Promise.all(promises)

// 実行には3000ミリ秒かかる
array // ["First", "Second", "Third"]
```

詳細は[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)をご覧ください。


## `Promise.allSettled`

- 履行: **全て**のプロミスが**決定**されたとき
- 拒否: このプロミスは拒否されない

:::note
決定というのはプロミスが履行か拒否されたときです。
つまり`allSettLed`の戻り値は、与えられた全てのプロミスが**履行か拒否**されたときに履行されます。
:::

また、`allSettled`の戻り値のプロミスは、以下のオブジェクトが入った配列で履行されます。

- `status`: そのプロミスが履行（`fulfilled`） or 拒否（`rejected`）されたか
- `value`: 履行されたときの値、`status`が`fulfilled`のときのみ存在
- `reason`: 拒否された理由、`status`が`rejected`のときのみ存在

例えばこのような感じです。
```js
const promises = [
  new Promise(resolve => setTimeout(() => resolve("First"), 1000)),
  // 2000ms経ったら拒否されるプロミス
  new Promise((_, reject) => setTimeout(() => reject("Second"), 2000)),
  new Promise(resolve => setTimeout(() => resolve("Third"), 3000))
];

const array = await Promise.allSettled(promises)

// arrayの中身:
[
  { "status": "fulfilled", "value": "First" },
  { "status": "rejected", "reason": "Second" }, // rejectedになっている
  { "status": "fulfilled", "value": "Third" }
]
```

詳細は[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled)をご覧ください。


## `Promise.any`

-  履行: **いずれか**のプロミスが履行されたとき
-  拒否: **全て**のプロミスが拒否されたとき

`any`は**いずれかのプロミスを履行する**ためのメソッドと言い換えられると思います。
全てのプロミスが拒否=どのプロミスも履行できなくなって、初めて拒否されるイメージです。

また、`any`の戻り値のプロミスは、与えられた中で最初に履行されたプロミスで履行されます。

```js
const promises = [
  new Promise((_, reject) => setTimeout(() => reject("First"), 1000)),
  new Promise(resolve => setTimeout(() => resolve("Second"), 2000)),
  new Promise((_, reject) => setTimeout(() => reject("Third"), 3000))
];

const result = await Promise.any(promises)
// 2000msかかる
result // 'Second'
```

詳細は[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/any)をご覧ください。

## `Promise.race`

- 履行: **いずれか**のプロミスが履行されたとき
- 拒否: **いずれかの**プロミスが拒否されたとき

`race`はいずれかのプロミスが決定されたときに決定されます。
これはつまり、**どのプロミスが最初に決定されるかを競うレース**のようなものと捉えられそうです。

```js
const promises = [
  new Promise((_, reject) => setTimeout(() => reject("First"), 1000)),
  new Promise(resolve => setTimeout(() => resolve("Second"), 2000)),
  new Promise((_, reject) => setTimeout(() => reject("Third"), 3000))
];

await Promise.race(promises)
// エラー: Uncaught First
```

詳細は[MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise/race)をご覧ください。

## `for await...of`

`Promise`の静的メソッドではありませんが、似たような用途に使える構文として[`for await...of`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/for-await...of)が挙げられます。

これは`Promise`の静的メソッドと違ってまとめて実行結果を返すのではなく、プロミスの実行結果を使って**何かの処理**を行えます。

`fof...of`に慣れ親しんでいる方からすれば、説明するより実際に見た方がわかりやすいかもしれません。
```js
const promises = [
  new Promise(resolve => setTimeout(() => resolve("First"), 1000)),
  new Promise(resolve=> setTimeout(() => resolve("Second"), 3000)),
  new Promise(resolve => setTimeout(() => resolve("Third"), 2000)),
];

// forの後にawaitをつける
for await (const value of promises) {
  // プロミスの実行結果であるvalueを使った処理
  console.log(value)
}

// 1000ms後: First
// さらに2000ms後: Second
// さらに0ms後: Third
```


# 並列になるか逐次になるか
JavaScriptのプロミスは以下の特徴を持っています。

- プロミスの実行は作成された時点（`new`された時点）から開始される
- 一度作ったプロミスの実行を止める方法は、**プロミスからは用意されていない**

`new`された時点とは言葉通りの意味で、例えばこんな感じになります。

```js
const promises = [
  new Promise(resolve => setTimeout(() => {
    console.log("First")
    resolve()
  }, 1000)),
  new Promise(resolve => setTimeout(() => {
    console.log("Second")
    resolve()
  }, 2000)),
  new Promise(resolve => setTimeout(() => {
    console.log("Third")
    resolve()
  }, 3000)),
];

// 3つのプロミスの実行はすでに始まっている
// 開始から1000ミリ秒後: First
// 開始から2000ミリ秒後: Second
// 開始から3000ミリ秒後: Third
```

ここでは配列の中に３つのプロミスを作成しています。
プロミスは`new`した時点で実行開始されるので、一見ただ配列を作っただけに見えても`setTimeout`は実行されます。

つまり、同時に作成されたプロミスは並列実行のように振る舞います。

:::note warn
JavaScriptはシングルスレッドなので、実際に行われる処理は1つです。
そのため実際には並列実行されないこともあります。

> JavaScript はもともとシングルスレッドなので、異なるプロミス間で制御が移り、プロミスの実行が同時に行われるように見えても、指定された瞬間には 1 つのタスクしか実行されないことに注意してください。JavaScript で並列実行を行うには、ワーカースレッドを使うしかありません。 - [MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise#プロミスの並行処理)

なお、今回使用している`setTimeout`は、（シングルスレッドでも）複数のタイムアウトを一度に実行できます。
そのためこの影響は受けないと思っていいと思います。
:::

## 逐次実行するには？
プロミスを逐次実行したければ、**プロミスを作成するタイミング**を逐次にする必要があります。
一度にまとめて作成するのではなく、必要になったときに順番に作成する感じです。

例えば、1000ms経ったらログに文字を出力する処理を考えます。
この処理は関数として実装し、関数は都度作成したプロミスを返します。
これは以下のような実装でできます。

```js
function run(msg) {
  return new Promise(resolve => {
    console.log(`start ${msg}`)
    setTimeout(() => {
      console.log(`done ${msg}`) // 1000ms経ったらログを出力する
      resolve(msg) // 与えられた文字列で解決する
    }, 1000)
  })
}
```

### 逐次実行になる例
一番単純な方法は、`for...of`文の中に`await`を書くことです。
`await run(msg)`のように順番にプロミスを作成していけば、簡単に逐次実行できます。

```js
const messages = ['A', 'B', 'C']

for (const msg of messages) {
　// 前のループが終わるまでプロミスを作成しない
  await run(msg)
}

console.log('done all') // 1000 * 3 = 3000ms後に実行される
```

```js:出力
// 開始直後
start A
// 1000ms後
done A
start B
// さらに1000ms後
done B
start C
// さらに1000ms後
done C
done all
```

`for`文の中で`await`を書いた場合、`await`したプロミスの実行が終わるまで次のループに行きません。
そのためプロミスが順番に作成され、その結果逐次実行になったというわけです。

### 並列実行になる例
しかし、この関数を以下のように呼び出してしまうと、逐次実行できません。

```js
const messages = ['A', 'B', 'C']

// 同時にプロミスを作成してしまう
const promises = messages.map(msg => run(msg))

for (const promise of promises) {
  await promise // 実行が終わるまで待つ
}

console.log('done all') // 1000 * 3 = 3000ms後に実行されるつもりで書いている
```
```js:出力
// 開始直後
start A
start B
start C
// 1000ms後
done A
done B
done C
done all
```

これは`messages.map(msg => run(msg))`の時点で3つのプロミスを**同時に作成してしまっている**からです。
その結果、全てのプロミスの実行が同時に始まってしまいました。

参考: [この記事](https://qiita.com/mellbrother/items/715ff5dce5845381eeee#comment-202a45c5d467a5dd67dc)

## 逐次実行する方法

先ほど紹介したように、普通のループ内でプロミスを作成して`await`すれば逐次実行できます。
それ以外にも、`Array.fromAsync`が便利なメソッドとして使えます。

[`Array.fromAsync`](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/fromAsync)は配列などから新しい配列を作成する静的メソッドです。
この際要素にプロミスが含まれていた場合、それを解決した値が使われます。

- 引数: 反復可能オブジェクト
- 戻り値: 新しく作成された配列で履行されるプロミス

これは割と`Promise.all`に似ています。
後述の[マッピング関数](#マッピング関数)を使わない場合、動作はほぼ同じです。

```js
const messages = ['A', 'B', 'C']

// プロミスの入った配列を作成
const promises = messages.map(msg => run(msg))

// この2つはどちらも並列実行される
const result = await Promise.all(promises)
const result = await Array.fromAsync(promises)
```

:::note
`Array.fromAsync`が取ることができる引数は反復可能オブジェクトだけではありません。
以下のようなものが使えます。
- [非同期反復可能](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Iteration_protocols#非同期イテレーターと非同期反復可能プロトコル)オブジェクト
- 反復可能オブジェクト（配列など） <- 今回使っているやつ
- Array-Like（配列風）オブジェクト
:::

### マッピング関数
`Array.fromAsync`で逐次実行する場合、第二引数の`mapFn`が使えます。
ここには関数が入り、これは配列のそれぞれの要素のマッピングします。

そして、`mapFn`の戻り値をプロミスにした場合、**プロミスの決定を待機（要するに`await`）してから次に行きます。**
（おそらく）待機が終わるまでプロミスが作成されないので、逐次実行に利用できます。

要するに、逐次実行できるよう、内部でいい感じにプロミスを作成してくれるということです。

例えば、先ほどの`Array.fromAsync`を逐次実行で動かすには、以下のように書き換えられます。

```js
const messages = ['A', 'B', 'C']

// messagesを使い、mapFnにプロミスを返す関数を入れる
await Array.fromAsync(messages, msg => run(msg))
```
```js:出力
// 開始直後
start A
// 1000ms後
done A
start B
// さらに1000ms後
done B
start C
// さらに1000ms後
done C
```

<!--
疲れたのでこれは諦めた
余裕があったら書くかもしれない

# 非同期反復可能オブジェクト

```js
function sleep(sec: number) {
  return new Promise(resolve => {
    setTimeout(resolve, sec)
  })
}

async function* test() {
  await sleep(500)
  yield 1

  await sleep(1000)
  yield 2

  await sleep(1500)
  yield 3

  await sleep(2000)
}
```

- 逐次実行になる

使えるやつ：

- `Array.fromAsync`; OK
- `for await...of`: OK
- `for...of`、`Promise`の静的メソッド、その他: NG

-->