【亿码酷站-编程开发教程】收集全网优质教程及源码资源!

全网优质软件开发、平面设计等教程及精品源码资源一站可得,www.ymkuzhan.com!

深入研究Node.js中的异步生成器和异步迭代_亿码酷站_编程开发技术教程插图

生成器函数在 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 的循环和可迭代对象的》 这两篇文章。在使用异步生成器之前,你需要对生成器和 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 来自第三方 API 或库的函数。为了能让大家轻松掌握,我们的例子尽量保持简单。

如果尝试运行上述程序,则会遇到问题:

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

JavaScript 告诉我们这个生成器是“不可迭代的”。乍一看,似乎使生成器函数异步也意味着它生成的生成器是不可迭代的。这有点令人困惑,因为生成器的目的是生成“以编程方式”可迭代的对象。

接下来搞清楚到底发生了什么。

检查生成器

如果你看了 Javascript 生成器这篇文章 ,那么就应该知道,如果对象定义了 Symbol.iterator 方法,并且该方法返回,则它在 javascript 中是一个实现了迭代器协议的可迭代对象。当对象具有 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 方法:

// File: test-program.js

/* ... */

const main = () => {
  const generator = createGenerator()
  const asyncGenerator = createAsyncGenerator()

  console.log('generator:',generator.next())
  console.log('asyncGenerator',asyncGenerator.next())
}
main()

则会看到另一个问题:

$ node test-program.js
generator: { value: 'a', done: false }
asyncGenerator Promise { <pending> }

为了使对象可迭代,next 方法需要返回带有 value 和 done 属性的对象。一个 async 函数将总是返回一个 Promise 对象。这个特性会带到用异步函数创建的生成器上——这些异步生成器始终会 yield 一个 Promise 对象。

这种行为使得 async 函数的生成器无法实现 javascript 迭代协议。

异步迭代

幸运的是有办法解决这个矛盾。如果看一看 async 生成器返回的构造函数或类

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

  console.log('asyncGenerator',asyncGenerator)
}

可以看到它是一个对象,其类型或类或构造函数是 AsyncGenerator 而不是 Generator:

asyncGenerator Object [AsyncGenerator] {}

尽管该对象有可能不是可迭代的,但它是异步可迭代的。

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

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

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

for await … of 循环

只用生成器的 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中文网如有侵权请联系admin#tyuanma.cn删除

25道初级Vue.js面试问题

下载声明:
  • 本站资源如无特殊说明默认解压密码为www.ymkuzhan.com建议使用WinRAR解压;
  • 本站资源来源于用户分享、互换、购买以及网络收集等渠道,本站不提供任何技术服务及有偿服务,资源仅提供给大家学习研究请勿作它用。
  • 赞助本站仅为维持服务器日常运行并非购买程序及源码费用因此不提供任何技术支持,如果你喜欢该程序,请购买正版!
  • 版权声明:
  • 下载本站资源学习研究的默认同意本站【版权声明】若本站提供的资源侵犯到你的权益,请提交版权证明文件至邮箱ymkuzhan#126.com(将#替换为@)站长将会在三个工作日内为您删除。
  • 免责声明:
  • 您好,本站所有资源(包括但不限于:源码、素材、工具、字体、图像、模板等)均为用户分享、互换、购买以及网络收集而来,并未取得原始权利人授权,因此禁止一切商用行为,仅可用于个人研究学习使用。请务必于下载后24小时内彻底删除,一切因下载人使用所引起的法律相关责任,包括但不限于:侵权,索赔,法律责任,刑事责任等相关责任,全部由下载人/使用人,全部承担。以上说明,一经发布视为您已全部阅读,理解、同意以上内容,如对以上内容持有异议,请勿下载,谢谢配合!支持正版,人人有责,如不慎对您的合法权益构成侵犯,请联系我们对相应内容进行删除,谢谢!