

今、 Promise の配列があったとき、その resolve の早い順に await したいと思っています。 ES2021 に、 `for await
of` の構文が導入されたので、 Promise の配列をその早い順に resolve してくれる async generator
に変換するライブラリなどの関数があれば、やりたいことが実現できるな、と思っています。

[code]

    // 例
    const promiseArray: Promise<any>[] = createPromises()
    
    for await (const val of promiseArray2generator(promiseArray)) {
      console.log(val)
      // val を使った処理いろいろ
    }
    
[/code]

# 質問

Promise の配列を async generator
に変換するのは、割と素直に考えると、ライブラリとして何かしら実装があってほしい機能なような気がしています。であるならば、有名なライブラリなどでこの機能が実現されていてもおかしくないと思っています。
(車輪の再発明をあまり行いたくない)

なので質問ですが、この「Promise の配列を async generator
に変換する」を実現する決まりきったイディオムや、これを実現するライブラリ(の関数)などはありますでしょうか。


CC BY-SA 4.0
2022-07-07T08:13:51.480
89805
2022-07-10T03:24:03.177
754
question
javascript typescript promise
Promise の配列に対して、それを早い順で resolve する async generator に変換したい


今のところ、直接的なイディオムなどは無さそうです。

### 多くの場合、普通の（非同期ではない）繰り返しで十分

例えば `for await` ではない普通の `for` でも、質問のコードと概ね同じことが行なえます。

[code]

    for (const p of promiseArray) {
        p.then(async (val) => {
            console.log(`val = "${val}"`)
            // val を使った処理いろいろ
        })
    }
    
[/code]

全体を `await` したい場合などは `map` の方がよいかも知れません。

[code]

    await Promise.all(
        promiseArray.map(async (p) => {
            const val = await p
            console.log(`val = "${val}"`)
            // val を使った処理いろいろ
        }),
    )
    
[/code]

これらの繰り返しそれ自体は同期的であり、Promiseの配列をただ配列の順番のまま扱います。しかし、繰り返し中に登録されたコールバック関数は、準備が出来たPromiseから呼ばれていくので、概ね質問の目的通りです。

これらで十分か、これらの方がよい状況が多いので、非同期ジェネレータ化するイディオムが生まれないのだと推測します。

### 上記の繰り返しコードと、質問のコード（`for await` \+ 非同期ジェネレータ）の違い

上記の繰り返しによるコードは「質問のコードと概ね同じ」と書きましたし、形も似ていると思います。しかし、違う点もあります。それは、「`// val
を使った処理いろいろ`」の部分で非同期処理を `await` した場合です。

  * 上記の繰り返しコードでは、各コールバックが非同期的に実行される:  
`await`
が書かれるのは、各コールバック関数の中になります。そこでコールバック関数の実行はプロックされますが、各コールバック関数は独立して呼び出されていますので、あるコールバック関数が
`await` している間に、他のコールバック関数が動くなど、非同期的に処理が進みます。

  * 質問のコードでは、各ループが逐次的に進む:  
`await` が書かれるのは、`for await` ループの中になります。そこでループはブロックされ、`await`
された非同期処理が解決するまで、次の要素の処理に進みません。

どちらが好ましいかは状況次第と思いますが、一見では逆と勘違いするかも知れないので注意が必要です。

### 質問のコード（`for await` \+ 非同期ジェネレータ）を実現するには

イディオムが見付からず、しかし一から書きたくない場合は、何らかの基盤が必要です。基盤としては、非同期で値の出し入れが出来るキューがよいと思います。

例えば、値の追加（`enqueue`） と 引き出し（`dequeue`）ができる `AsyncSimpleQueue`
があったとすると、下のように書けます。

[code]

    async function* promiseArrayToGenerator<T>(promiseArray: Promise<T>[]) {
        const queue = new AsyncSimpleQueue<T>()
    
        for (const p of promiseArray) {
            p.then((val: T) => {
                queue.enqueue(val)
            })
        }
    
        for (let i = 0; i < promiseArray.length; i++) {
            yield await queue.dequeue()
        }
    }
    
[/code]

### 非同期キューの書き方

非同期のキューは使い道が多いので、ライブラリなどが有るはずです。しかし、検索すると非同期タスクを管理するタスクキューばかりが出て来てしまいます。ですので、最小限の実装を出しておきます。

[code]

    class AsyncSimpleQueue<T> {
        #valQueue: T[] = []
        #resolverQueue: ((val: T) => void)[] = []
    
        enqueue(val: T): void {
            const resolver = this.#resolverQueue.shift()
            if (resolver) {
                resolver(val)
            } else {
                this.#valQueue.push(val)
            }
        }
    
        dequeue(): Promise<T> {
            if (this.#valQueue.length > 0) {
                return Promise.resolve(this.#valQueue.shift()!)
            }
            return new Promise<T>((resolve) => {
                this.#resolverQueue.push(resolve)
            })
        }
    }
    
[/code]

上のコードは、書籍[『JavaScript
第7版』](https://www.oreilly.co.jp/books/9784873119700/)の「13.4.4
非同期イテレータの実装」を参考にしたものです。そこで紹介されている `AsyncQueue`
は、それ自体が非同期イテレータでもあり、キューを閉じる機能もあります。本の筆者は、[Rauschmayer博士のブログ](https://2ality.com)を参考にしたそうです。


CC BY-SA 4.0
2022-07-10T03:24:03.177
89877
2022-07-10T03:24:03.177
3054
89805
answer