【JavaScript】AsyncIteratorを紐解いてみる

## はじめに

  参考書で出てきた **「AsyncIterator」** についていまいち有用性が分からず、
  個人的にまとめてみたいと思います。

  あくまで理解に努める形なので、もっと深い部分まで知りたい方はMDNを見ていただければと思います。
  [AsyncIterator-MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/AsyncIterator)
  
  ※誤りありましたら、ご指摘いただけますと幸いです。

### Iteratorとは
  まずそもそも、**Iterator** とは。
  めちゃ簡単に言うと**データを1つずつ順番に取り出せる仕組み**って感じです。

  例えば、配列に対して **for...of** でループを回すと順番に値を取得することができます。
  これは、配列が **Symbol.iterator** という特別なメソッドを持っているためです。
  内部的にはfor...ofループ内で **next()** メソッドが呼び出されることで、データを1つずつ取得できます。

  詳細は割愛しますが、 Iterator は手動で実装することが可能です。

### ではそれを踏まえてAsyncIteratorとは
  ここも超ざっくりになりますが、違いとしては**同期的**か**非同期的**であるか、という部分になります。
  Iteratorは同期的に値を取得できるのに対して、AsyncIteratorでは非同期的にデータを取得する（ただし**await**）形になります。

  これはコードを見た方が早いと思うので、実際に動かして比較してみます。

## 実装してみる
  まずIteratorの場合。
  
```javascript:iterator.js
function fetchDataWithoutAsyncIterator() {
    for (let i = 1; i <= 5; i++) {
        setTimeout(() => {
            console.log(`Received: ${i}`);
        }, Math.random() * 3000); // 0〜3秒のランダム遅延を起こす
    }
}

fetchDataWithoutAsyncIterator();

```

上記の実行結果は下記になります。

![image.png](http://hr-dash.tech/files/img/2025-03/20250302165652775.png)

非同期で処理している部分が待機されず、ランダムに値が出力されてしまいました。

では続いてAsyncIteratorの場合。

```javascript:asyncIterator.js
async function* fetchDataWithAsyncIterator() {
    for (let i = 1; i <= 5; i++) {
        await new Promise(resolve => setTimeout(resolve, Math.random() * 3000)); // ランダムな待機
        yield i; // 取得したデータを1つずつ返す
    }
}

(async () => {
    for await (const num of fetchDataWithAsyncIterator()) {
        console.log(`Received: ${num}`);
    }
})();

```

実行結果は下記になります。

![image.png](http://hr-dash.tech/files/img/2025-03/20250302165816485.png)

画像なので実際には見えませんが、1~3秒ランダムスリープはあるものの、しっかり待機されてから1~5まで順番に出力されました。

ちなみに **「for await (...of...)」**はAsyncIterator内で使用できるループで、Promiseが戻ってくる場合の処理も問題なく対応することができます。

#### おまけ：for...of内でasync/awaitでいいのでは？
上記のコードを見た上で、「別にfor...ofループ内でawaitで待機させればいいのでは？」という疑問が浮かんだ方もいらっしゃるかと思います。確かに今回のケースで言えば、その形でも問題なく実装できそうです。

**「for...of」** と **「for await (...of...)」**の大きな違いは、データが既に手元にあるか否かです。

既に手元にあるのであれば「for...of」内でawaitを使用して、問題なく非同期処理であっても待機させればOKです。
一方**処理を開始した後に、順番に非同期で届く**場合、この場合は「for await (...of...)」を使用するが吉ということになります。

例えば、fetch()でAPI を提供するサーバーへのリクエストが発生する場合、ここでの結果が非同期で届く場合なんかは、AsyncIterator での for await (...of...) ループが有用となるでしょう。

下記でも触れてみます。


## Web APIにおける有用性
上記を見ただけではだから何？という感じですが、実際にWeb APIを想定してみると有用性が分かりやすいと思います。
実際に見てみましょう。

```javascript:asyncIteratorWithWebAPI.js
async function* fetchRamensPaginated() {
    let page = 1;
    while (true) {
        const response = await fetch(`https://ramendatabase/ramens?page=${page}`);
        const ramens = await response.json();
        
        if (ramens.length === 0) break; // データがなくなったら終了
        
        for (const ramen of ramens) {
            yield ramen; // 1件ずつ返す
        }

        page++; // 次のページへ
    }
}

(async () => {
    for await (const ramen of fetchRamensPaginated()) {
        console.log(ramen.name); // 1件ずつ取得・処理
    }
})();

```

上記のケースはページネーションAPIを想定しています。
データが大量にあると、一括取得ではメモリ負荷が高くなり、処理が遅くなるケースがあります。

通常の fetch で全データを一括取得を行うと、メモリがクラッシュするケースも考えられますが、
上記のようにyieldでデータを取得できるたびにデータを返してあげることによって負荷分散が可能となります。

ちなみに for await(...of...) 内で break を使えば、適切なタイミングで処理を中断できます。

  

## 最後に
  非同期処理については、JSがシングルスレッドである以上、絶対に理解しなければいけない分野になると思います。
  バックエンドが API を通じてデータを提供し、クライアントとやり取りを行う構成となると、ここでいかに非同期処理をうまいこと使えるかでUXの向上にもつながります。

  この分野は特に直感的な理解がしづらい分野だと思うので、特に手を動かしながら理解に努めていきたいと思います。