JS の for await...of の使いどころ

# for await...of

こういう書き方ご存知でしょうか。

```js
for await (const item of asyncIterable) {
  ...
}
```

for の直後に await が書かれています。ECMAScriptの正式な記法ですが、使わなければいけないタイミングが少なくあまり認知されていない気がします。自分は最近知りました。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/for-await...of

端的に言うとPromiseを直列にループしたい時の記法です。

ただ、重要なのは `AsyncIterable` を扱う場合であり、シンプルなPromiseの配列を回す時は通常の for...of の中で await する方法でも特に大きな問題はありません。

# 結論

使う必要があるタイミングは

- `AsyncIterable` なオブジェクトを回す時

使っても良いタイミングは

- Promiseの配列を直列に回したい時

使ってはいけないタイミングは

- 複数のPromiseを順番関係なく全部待ちたい時

# `AsyncIterable`

基本的に for await...of は `AsyncIterable` のためにあると言って良いと思います。
`AsyncIterable` は非同期処理を直列に待つためのプロトコルです。

分かりやすい実例を挙げると、OpenAI の ChatCompletion の API には、ストリーミングでレスポンスを受け取る機能があります。
GPTが生成するテキストを、全部生成し終わってから受け取るのではなく、トークン（≒文字、もしくは単語のような区切り）ごとに受け取ることができるので、ユーザーの待機時間を感覚的に短くすることができます。

その API の [Node のライブラリ](https://github.com/openai/openai-node)の書き方は例えばこんな感じです。

```typescript
// README からコピペ
const stream = await openai.chat.completions.create({
  model: 'gpt-4',
  messages: [{ role: 'user', content: 'Say this is a test' }],
  stream: true,
});
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content || '');
}
// トークンが（まるで人間がタイピングしているかのように）一つ一つ標準出力に表示される
```

このケースの場合、非同期な処理を順番に待つことが重要です。
なぜならLLMが生成するのは自然言語なので、トークンを生成された順番に並べないと意味不明な文章が出来上がるからです。

**そして、上記の `stream` は `AsyncIterable` なオブジェクトです。**
逆 `Iterable` でも `ReadableStream` でもないので、通常の for...of で回したり `stream.getReader()` したりすることはできません。

記法を一つに絞っているところに意思を感じるので個人的には好きです。
LLMのAPIのレスポンスに関しては本当にただただストリーミングされたトークンを受け取って逐次表示する以外の用法は無いはずなので、シンプルで良いですよね。

## `ReadableStream`

https://developer.mozilla.org/ja/docs/Web/API/ReadableStream

JS にはストリームを受け取るための `ReadableStream` というクラスが存在します。例えば `fetch` のレスポンスの `body` が `ReadableStream` です。
`ReadableStream` は `AsyncIterable` プロトコルを実装しているので、 for await...of で直列に回すことができます。

for await...of を使わずに `getReader()` を使うと少々プリミティブな書き味になりアプリケーションコードとしては微妙に使い勝手が悪いです。
一方で for await...of を使うと、単なる配列を同期的にループするような感覚でストリーミングのレスポンスを受け取ることができます。

が、現時点では `ReadableStream` に `AsyncIterable` が実装されているランタイムは結構限られています。
ブラウザでほぼ（？）使えないのがツラいですね。
（MDN見る限りFireFoxは使えるっぽいんですが、試したところ動かず。多分Experimentalフラグとかをいじらないとダメな段階かな）
Nodeではv16.5.0から入っているようなので、サーバアプリケーションのコードでなら問題ないかと思います。

# Promiseの配列を扱う場合

`AsyncIterable` でなくとも、複数の Promise を直列に await して処理したい場面というのは実際問題あります。
例えば複数のAPIを順番に繋ぐ必要があるケースで考えます。

```typescript
const apiResponsePromises = [fetch("api1"), fetch("api2"), fetch("api3")];
```

こういう複数のPromiseを直列に待ちたい場合、最もポピュラーで簡単で可読性が良いのは for...of を使って await する方法だと思います。

```typescript
for (const responsePromise of apiResponsePromises) {
  const response = await responsePromise;
  response.xxx
  ...
}
```

この書き方なら直列で待つことができ、独自の関数を用意する必要もなく、可読性も良いです。
（直列に待ちたい場合に `forEach` などが使えない理由はここでは流石に省きます）

でこの場合に for await...of を使ってもOKです。

```typescript
for await (const response of apiResponsePromises) {
  response.xxx
  ...
}
```

MDN が分かりやすいですが、for await...of の処理の一部を抜き出すと以下の感じです。

1. 対象のオブジェクトが `AsyncIterable` かどうか判定。
1. 対象のオブジェクトが `AsyncIterable` ではない場合、通常の for...of と同様に、対象のオブジェクトを `Iterable` なオブジェクトとしてループする。
2. その Iterable の値が Promise なら、await した上で変数に格納する。

この使い方に関しては通常の for...of + await のシンタックスシュガーと言えるでしょう。

なので、この場合は for await...of を使う方が記述量は減ってスッキリはします。

がしかし、前述の通り認知度の低い記法なので、驚き最小の原則的な意味で個人的には for...of で書いてても全く問題ないと感じます。
そもそも for 文を使っている時点で今のJS/TSの流れだとちょっと抵抗あったりしますし、スッキリすると言っても1行だけで、変数のスコープを狭くできるわけでも無いので...

あるいはチームみんなで共有して使ってこ〜みたいなのが良いかもしれないですね。

## 順番関係なく全部待つ場合

これはもう色々なところで説明されているので細かい説明は不要かと思います。

`Promise.all` か `Promise.allSettled` を使いましょう。

# おしまい

ということで、直列Promiseにちょっと強くなりましたね。