ホームページ  >  記事  >  ウェブフロントエンド  >  Node.js の非同期ジェネレーターと非同期反復について詳しく説明します。

Node.js の非同期ジェネレーターと非同期反復について詳しく説明します。

青灯夜游
青灯夜游転載
2020-09-19 10:20:482173ブラウズ

Node.js の非同期ジェネレーターと非同期反復について詳しく説明します。

JavaScript でのジェネレーター関数の登場は、async/await の導入よりも前です。つまり、非同期ジェネレーター (常に Promise を返し、 await ジェネレーター)、注意が必要な点も多数紹介されています。

今日は、非同期ジェネレーターと、その近縁種である非同期反復について見ていきます。

: これらの概念 は最新の仕様に従うすべての JavaScript に適用される必要がありますが、この記事のすべてのコードは Node.js 10、12、および 14 バージョンに固有のものです。開発され、テストされました。

ビデオ チュートリアルの推奨:

node js チュートリアル

非同期ジェネレーター関数

この小さなプログラムを見てください:

// 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()

また、実際の作業では、これは行われません。サードパーティの API またはライブラリからの関数を

await する可能性があります。誰もが理解しやすいように、例はできるだけシンプルにしています。

上記のプログラムを実行しようとすると、問題が発生します。

$ node main.js
/Users/alanstorm/Desktop/main.js:9
  for (const item of generator) {
                     ^
TypeError: generator is not iterable

JavaScript は、このジェネレーターが「反復不可能」であることを示しています。一見すると、ジェネレーター関数を非同期にするということは、生成されるジェネレーターが反復可能ではないことも意味するように思えます。ジェネレーターの目的は「プログラム的に」反復可能なオブジェクトを生成することなので、これは少し混乱します。

次のステップは、何が起こったのかを解明することです。

ジェネレーターを確認する

Javascript ジェネレーターの記事を読むと、オブジェクトが Symbol.iterator メソッドを定義しているかどうかがわかります。メソッドが返す場合、これは JavaScript で iterator プロトコル を実装する反復可能なオブジェクトです。オブジェクトに next メソッドがある場合、オブジェクトはイテレータ プロトコルを実装し、next メソッドは valuedone## を持つプロパティを返します。 # いずれかのプロパティ、または 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 メソッド <pre class="brush:js;toolbar:false">// File: test-program.js /* ... */ const main = () =&gt; { const generator = createGenerator() const asyncGenerator = createAsyncGenerator() console.log(&amp;#39;generator:&amp;#39;,generator.next()) console.log(&amp;#39;asyncGenerator&amp;#39;,asyncGenerator.next()) } main()</pre> を呼び出すようにテスト コードを変更すると、別の問題が発生します: <pre class="brush:js;toolbar:false">$ node test-program.js generator: { value: &amp;#39;a&amp;#39;, done: false } asyncGenerator Promise { &lt;pending&gt; }</pre>オブジェクトを反復可能にするには、

next

メソッドは、

value

および done プロパティを持つオブジェクトを返す必要があります。 async 関数は常に Promise オブジェクトを返します。この機能は、非同期関数で作成されたジェネレーターに引き継がれます。これらの非同期ジェネレーターは、常に yield Promise オブジェクトを生成します。 この動作により、async 関数のジェネレーターは JavaScript 反復プロトコルを実装できなくなります。

非同期反復 幸いなことに、この矛盾を解決する方法があります。

async

ジェネレーターによって返されたコンストラクターまたはクラス

// File: test-program.js
/* ... */
const main = () => {
  const generator = createGenerator()
  const asyncGenerator = createAsyncGenerator()

  console.log(&#39;asyncGenerator&#39;,asyncGenerator)
}

を見ると、その型、クラス、またはコンストラクターが AsyncGenerator であるオブジェクトであることがわかります。 while Not

Generator

: <pre class="brush:js;toolbar:false">asyncGenerator Object [AsyncGenerator] {}</pre>オブジェクトは反復可能ではないかもしれませんが、非同期反復可能です。

要想使对象能够异步迭代,它必须实现一个 Symbol.asyncIterator 方法。这个方法必须返回一个对象,该对象实现了异步版本的迭代器协议。也就是说,对象必须具有返回 Promisenext 方法,并且这个 promise 必须最终解析为带有 donevalue 属性的对象。

一个 AsyncGenerator 对象满足所有这些条件。

这就留下了一个问题——我们怎样才能遍历一个不可迭代但可以异步迭代的对象?

for await … of 循环

只用生成器的 next 方法就可以手动迭代异步可迭代对象。 (注意,这里的 main 函数现在是 async main ——这样能够使我们在函数内部使用 await

// File: main.js
const createAsyncGenerator = async function*(){
  yield await new Promise((r) => r(&#39;a&#39;))
  yield &#39;b&#39;
  yield &#39;c&#39;
}

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(&#39;a&#39;))
  yield &#39;b&#39;
  yield &#39;c&#39;
}

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 解析为 donetrue 的对象,然后在 for await 循环之后继续执行代码。

下面的例子可以说明一点:

let count = 0
const getCount = () => {
  count++
  return `${count}. `
}

const createAsyncGenerator = async function*() {
  console.log(getCount() + &#39;entering createAsyncGenerator&#39;)

  console.log(getCount() + &#39;about to yield a&#39;)
  yield await new Promise((r)=>r(&#39;a&#39;))

  console.log(getCount() + &#39;re-entering createAsyncGenerator&#39;)
  console.log(getCount() + &#39;about to yield b&#39;)
  yield &#39;b&#39;

  console.log(getCount() + &#39;re-entering createAsyncGenerator&#39;)
  console.log(getCount() + &#39;about to yield c&#39;)
  yield &#39;c&#39;

  console.log(getCount() + &#39;re-entering createAsyncGenerator&#39;)
  console.log(getCount() + &#39;exiting createAsyncGenerator&#39;)
}

const main = async () => {
  console.log(getCount() + &#39;entering main&#39;)

  const asyncGenerator = createAsyncGenerator()
  console.log(getCount() + &#39;starting for await loop&#39;)
  for await(const item of asyncGenerator) {
    console.log(getCount() + &#39;entering for await loop&#39;)
    console.log(getCount() + item)
    console.log(getCount() + &#39;exiting for await loop&#39;)
  }
  console.log(getCount() + &#39;done with for await loop&#39;)
  console.log(getCount() + &#39;leaving main&#39;)
}

console.log(getCount() + &#39;before calling main&#39;)
main()
console.log(getCount() + &#39;after calling main&#39;)

这段代码你用了编号的日志记录语句,可让你跟踪其执行情况。作为练习,你需要自己运行程序然后查看执行结果是怎样的。

如果你不知道它的工作方式,就会使程序的执行产生混乱,但异步迭代的确是一项强大的技术。

更多编程相关知识,请访问:编程入门!!

以上がNode.js の非同期ジェネレーターと非同期反復について詳しく説明します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。