JavaScript でのジェネレーター関数の登場は、async/await の導入よりも前です。つまり、非同期ジェネレーター (常に Promise
を返し、 await
ジェネレーター)、注意が必要な点も多数紹介されています。
今日は、非同期ジェネレーターと、その近縁種である非同期反復について見ていきます。
注 : これらの概念 は最新の仕様に従うすべての JavaScript に適用される必要がありますが、この記事のすべてのコードは Node.js 10、12、および 14 バージョンに固有のものです。開発され、テストされました。
ビデオ チュートリアルの推奨:非同期ジェネレーター関数この小さなプログラムを見てください:
// File: main.js const createGenerator = function*(){ yield 'a' yield 'b' yield 'c' } const main = () => { const generator = createGenerator() for (const item of generator) { console.log(item) } } main()
for ... of を使用してジェネレーター オブジェクトをループします。かなり標準的なものですが、これほど些細なことのために実際にジェネレーターを使用することはありません。ジェネレーターと
for ... of ループに詳しくない場合は、「
Javascript ジェネレーター」と「ES6 ループと反復可能オブジェクト 」の 2 つの記事を読んでください。 。非同期ジェネレーターを使用する前に、ジェネレーターと for ... of ループについてしっかりと理解する必要があります。
await を使用するとします。関数が
async キーワードで宣言される必要がある限り、Node.js はこの関数をサポートします。非同期関数に詳しくない場合は、記事「
モダン JavaScript での非同期タスクの作成」を参照してください。
await を使用します。
// File: main.js const createGenerator = async function*(){ yield await new Promise((r) => r('a')) yield 'b' yield 'c' } const main = () => { const generator = createGenerator() for (const item of generator) { console.log(item) } } main()
await する可能性があります。誰もが理解しやすいように、例はできるだけシンプルにしています。
$ node main.js /Users/alanstorm/Desktop/main.js:9 for (const item of generator) { ^ TypeError: generator is not iterable
Javascript ジェネレーターの記事を読むと、オブジェクトが Symbol.iterator メソッドを定義しているかどうかがわかります。メソッドが返す場合、これは JavaScript で
iterator プロトコル を実装する反復可能なオブジェクトです。オブジェクトに next メソッドがある場合、オブジェクトはイテレータ プロトコルを実装し、
next メソッドは
value、
done## を持つプロパティを返します。 # いずれかのプロパティ、または value
プロパティと done
プロパティの両方を持つオブジェクト。 次のコードを使用して、非同期ジェネレーター関数と通常のジェネレーター関数によって返されるジェネレーター オブジェクトを比較すると、
// File: test-program.js const createGenerator = function*(){ yield 'a' yield 'b' yield 'c' } const createAsyncGenerator = async function*(){ yield await new Promise((r) => r('a')) yield 'b' yield 'c' } const main = () => { const generator = createGenerator() const asyncGenerator = createAsyncGenerator() console.log('generator:',generator[Symbol.iterator]) console.log('asyncGenerator',asyncGenerator[Symbol.iterator]) } main()
、前の
には # がないことがわかります。# #Symbol.iterator メソッド、後者にはあります。
$ node test-program.js generator: [Function: [Symbol.iterator]] asyncGenerator undefined
両方のジェネレーター オブジェクト
には next メソッドがあります。この next メソッド <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false">// File: test-program.js
/* ... */
const main = () => {
const generator = createGenerator()
const asyncGenerator = createAsyncGenerator()
console.log(&#39;generator:&#39;,generator.next())
console.log(&#39;asyncGenerator&#39;,asyncGenerator.next())
}
main()</pre><div class="contentsignin">ログイン後にコピー</div></div>
を呼び出すようにテスト コードを変更すると、別の問題が発生します: <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false">$ node test-program.js
generator: { value: &#39;a&#39;, done: false }
asyncGenerator Promise { <pending> }</pre><div class="contentsignin">ログイン後にコピー</div></div>
オブジェクトを反復可能にするには、
メソッドは、
value および done
プロパティを持つオブジェクトを返す必要があります。 async
関数は常に Promise
オブジェクトを返します。この機能は、非同期関数で作成されたジェネレーターに引き継がれます。これらの非同期ジェネレーターは、常に yield
Promise
オブジェクトを生成します。 この動作により、
async
関数のジェネレーターは JavaScript 反復プロトコルを実装できなくなります。
非同期反復 幸いなことに、この矛盾を解決する方法があります。
// File: test-program.js /* ... */ const main = () => { const generator = createGenerator() const asyncGenerator = createAsyncGenerator() console.log('asyncGenerator',asyncGenerator) }
を見ると、その型、クラス、またはコンストラクターが AsyncGenerator
であるオブジェクトであることがわかります。 while Not
: <div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false">asyncGenerator Object [AsyncGenerator] {}</pre><div class="contentsignin">ログイン後にコピー</div></div>
オブジェクトは反復可能ではないかもしれませんが、非同期反復可能
です。
要想使对象能够异步迭代,它必须实现一个 Symbol.asyncIterator
方法。这个方法必须返回一个对象,该对象实现了异步版本的迭代器协议。也就是说,对象必须具有返回 Promise
的 next
方法,并且这个 promise 必须最终解析为带有 done
和 value
属性的对象。
一个 AsyncGenerator
对象满足所有这些条件。
这就留下了一个问题——我们怎样才能遍历一个不可迭代但可以异步迭代的对象?
只用生成器的 next
方法就可以手动迭代异步可迭代对象。 (注意,这里的 main
函数现在是 async main
——这样能够使我们在函数内部使用 await
)
// File: main.js const createAsyncGenerator = async function*(){ yield await new Promise((r) => r('a')) yield 'b' yield 'c' } const main = async () => { const asyncGenerator = createAsyncGenerator() let result = {done:false} while(!result.done) { result = await asyncGenerator.next() if(result.done) { continue; } console.log(result.value) } } main()
但是,这不是最直接的循环机制。我既不喜欢 while
的循环条件,也不想手动检查 result.done
。另外, result.done
变量必须同时存在于内部和外部块的作用域内。
幸运的是大多数(也许是所有?)支持异步迭代器的 javascript 实现也都支持特殊的 for await ... of
循环语法。例如:
const createAsyncGenerator = async function*(){ yield await new Promise((r) => r('a')) yield 'b' yield 'c' } const main = async () => { const asyncGenerator = createAsyncGenerator() for await(const item of asyncGenerator) { console.log(item) } } main()
如果运行上述代码,则会看到异步生成器与可迭代对象已被成功循环,并且在循环体中得到了 Promise
的完全解析值。
$ node main.js a b c
这个 for await ... of
循环更喜欢实现了异步迭代器协议的对象。但是你可以用它遍历任何一种可迭代对象。
for await(const item of [1,2,3]) { console.log(item) }
当你使用 for await
时,Node.js 将会首先在对象上寻找 Symbol.asyncIterator
方法。如果找不到,它将回退到使用 Symbol.iterator
的方法。
与 await
一样,for await
循环会将非线性代码执行引入程序中。也就是说,你的代码将会以和编写的代码不同的顺序运行。
当你的程序第一次遇到 for await
循环时,它将在你的对象上调用 next
。
该对象将 yield
一个 promise,然后代码的执行将会离开你的 async
函数,并且你的程序将继续在该函数之外执行。
一旦你的 promise 得到解决,代码执行将会使用这个值返回到循环体。
当循环结束并进行下一个行程时,Node.js 将在对象上调用 next
。该调用会产生另一个 promise,代码执行将会再次离开你的函数。重复这种模式,直到 Promise 解析为 done
为 true
的对象,然后在 for await
循环之后继续执行代码。
下面的例子可以说明一点:
let count = 0 const getCount = () => { count++ return `${count}. ` } const createAsyncGenerator = async function*() { console.log(getCount() + 'entering createAsyncGenerator') console.log(getCount() + 'about to yield a') yield await new Promise((r)=>r('a')) console.log(getCount() + 're-entering createAsyncGenerator') console.log(getCount() + 'about to yield b') yield 'b' console.log(getCount() + 're-entering createAsyncGenerator') console.log(getCount() + 'about to yield c') yield 'c' console.log(getCount() + 're-entering createAsyncGenerator') console.log(getCount() + 'exiting createAsyncGenerator') } const main = async () => { console.log(getCount() + 'entering main') const asyncGenerator = createAsyncGenerator() console.log(getCount() + 'starting for await loop') for await(const item of asyncGenerator) { console.log(getCount() + 'entering for await loop') console.log(getCount() + item) console.log(getCount() + 'exiting for await loop') } console.log(getCount() + 'done with for await loop') console.log(getCount() + 'leaving main') } console.log(getCount() + 'before calling main') main() console.log(getCount() + 'after calling main')
这段代码你用了编号的日志记录语句,可让你跟踪其执行情况。作为练习,你需要自己运行程序然后查看执行结果是怎样的。
如果你不知道它的工作方式,就会使程序的执行产生混乱,但异步迭代的确是一项强大的技术。
更多编程相关知识,请访问:编程入门!!
以上がNode.js の非同期ジェネレーターと非同期反復について詳しく説明します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。