この短い投稿では、EcmaScript標準の興味深い提案(英語の提案)について説明します。 非同期イテレーター、それが何であるか、それらを使用する方法、そして単純な開発者がそれらを必要とする理由について説明します。
非同期イテレーター。これは通常のイテレーターの機能の拡張であり、 for-of
/ for-await-of
ループの助けを借りて、コレクションのすべての要素を実行できます。
まず、ジェネレーターとイテレーターの意味を説明する価値があります。なぜなら、 これらの用語をよく使用します。 ジェネレーターはイテレーターを返す関数であり、イテレーターはnext()
メソッドを含むオブジェクトであり、次の値を返します。
function* generator () { // yield 1 } const iterator = generator() // console.log(iterator.next()) /// { value: 1, done: false }
イテレータについて詳しく説明し、現時点でのイテレータの意味を説明したいと思います。 最新のJavaScript(ES6 / ES7標準)では、コレクションの値( Array
、 Set
、 Map
など)を順番に繰り返すことができます。 このために、( Symbol
) Symbol.iterator
を使用してコレクションのプロトタイプで定義されているイテレータプロトコルが採用されました。
// , // Range function Range (start, stop) { this.start = start this.stop = stop } // , // , for-of Range.prototype[Symbol.iterator] = function *values () { for (let i = this.start; i < this.stop; i++) { yield i } } // const range = new Range(1, 5) // [Symbol.iterator]() // for (let number of range) { console.log(number) // 1, 2, 3, 4 }
各イテレータ(この場合、 range[Symbol.iterator]()
)には、2つのフィールドを含むオブジェクトを返すnext()
メソッドがあります: value
とdone
には、それぞれ現在の値とジェネレーターの終了を示すフラグが含まdone
ます。 このオブジェクトは、次のインターフェースで記述できます。
interface IteratorResult<T> { value: T; done: Boolean; }
ジェネレーターの詳細については、MDNを参照してください 。
ところで、既にイテレータがあり、 for-of
でそれを調べたい場合、それを私たちの(または他のイテレート可能な)型にキャストする必要はありません。 各イテレータには同じメソッド[Symbol.iterator]
、これを返します:
const iter = range[Symbol.iterator]() assert.strictEqual(iter, iter[Symbol.iterator]())
ここですべてが明確であることを願っています。 非同期関数についてもう少し説明する必要があります。
ES7では、 async
/ await
構文が提案されました。 実際、これは砂糖です。これにより、擬似同期スタイルでPromiseを操作できます。
async function request (url) { const response = await fetch(url) return await response.json() } // function request (url) { return fetch(url) .then(response => response.json()) }
通常の関数との違いは、 async
関数が常にPromiseを返すことです。たとえ通常のreturn 1
を作成しても、 Promise
を取得し、解決すると1
返します。
さて、いよいよ非同期イテレーターに移ります。
非同期関数( async function () { ... }
)に続いて、これらの関数内で使用できる非同期イテレーターが提案されました。
async function* createQueue () { yield 1 yield 2 // ... } async function handle (queue) { for await (let value of queue) { console.log(value) // 1, 2, ... } }
非同期イテレーターは現在、第3段階(候補)の文にあります。これは、構文が安定しており、標準への組み込みを待っていることを意味します。 この提案はどのJavaScriptエンジンにもまだ実装されていませんが、 Babelプラグインbabel-plugin-transform-async-generator-functionsを使用して、試してみることもできます 。
{ "dependencies": { "babel-preset-es2015-node": "···", "babel-preset-es2016": "···", "babel-preset-es2017": "···", "babel-plugin-transform-async-generator-functions": "···" // ··· }, "babel": { "presets": [ "es2015-node", "es2016", "es2017" ], "plugins": [ "transform-async-generator-functions" ] }, // ··· }
2alityブログから取られた、使用例の完全なコードはrauschma / async-iter-demoにあります
それでは、非同期イテレータは通常のものとどう違うのでしょうか? 前述のように、イテレーターはIteratorResult
値を返します。 非同期イテレーターは常にPromise<IteratorResult>
返します。 これは、値を取得し、サイクルの実行を継続するかどうかを理解するために、 IteratorResult
が返すプロミスの解決を待つ必要があることを意味します。 そのため、新しいfor-await-of
構文が導入され、このすべての機能が実行されます。
論理的な疑問が生じます:なぜ新しい構文を導入する必要があったのですか、なぜPromise<IteratorResult>
ではなくIteratorResult<Promise>
を返して、彼の手で待つ( await ...
)できないのですか(この奇妙な式をおawait ...
)? これは、次の値が次の値かどうかを同期ジェネレーターから判断できない場合に行われます。 たとえば、ネットワークを介してリモートキューに移動し、キューが空の場合は次の値を取得して、ループを終了する必要があります。
さて、私たちはそれを理解しました。最後の質問は残りました-非同期ジェネレーターとイテレーターの使用です。 ここではすべてが非常に簡単です。ジェネレータにasync
キーワードを追加し、 async
ジェネレータを取得します。
// async function* queue () { // while (true) { // const task = await redis.lpop('tasks-queue') if (task === null) { // , // , Promise<IteratorResult> return } else { // yield task } } } // async function handle () { // const tasks = queue() // for await (const task of tasks) { // console.log(task) } }
for-await-of
を使用for-await-of
て独自の構造を非同期的に反復するfor-await-of
、 [Symbol.asyncIterator]
メソッドを実装する必要があります。
function MyQueue (name) { this.name = name } MyQueue.prototype[Symbol.asyncIterator] = async function* values () { // , while (true) { const task = await redis.lpop(this.name) if (task === null) { return } else { yield task } } } async function handle () { const tasks = new MyQueue('tasks-queue') for await (const task of tasks) { console.log(task) } }
それだけです この記事がおもしろくて、少なくともある程度は役に立つことを願っています。 ご清聴ありがとうございました。