生成器函数在 javascript 中的出现早于引入 async/await,这意味着在创建异步生成器(始终返回 promise 且可以 await 的生成器)的同时,还引入了许多需要注意的事项。
今天,我们将研究异步生成器及其近亲——异步迭代。
注意:尽管这些概念应该适用于所有遵循现代规范的 javascript,但本文中的所有代码都是针对 node.js 10、12和 14 版开发和测试的。
视频教程推荐:node js教程
异步生成器函数
看一下这个小程序:
// file: main.jsconst 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.jsconst 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 iterablejavascript 告诉我们这个生成器是“不可迭代的”。乍一看,似乎使生成器函数异步也意味着它生成的生成器是不可迭代的。这有点令人困惑,因为生成器的目的是生成“以编程方式”可迭代的对象。
接下来搞清楚到底发生了什么。
检查生成器
如果你看了 javascript 生成器这篇文章 ,那么就应该知道,如果对象定义了 symbol.iterator 方法,并且该方法返回,则它在 javascript 中是一个实现了迭代器协议的可迭代对象。当对象具有 next 方法时,该对象将实现迭代器协议,并且该 next 方法返回带有 value 属性,done 属性之一或同时带有 value 和 done 属性的对象。
如果用下面这段代码比较异步生成器函数与常规生成器函数返回的生成器对象:
// file: test-program.jsconst 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.jsgenerator: [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.jsgenerator: { 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.jsconst 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) {
无法链接使用代理服务器也不行北京服务器租用技术云空间网站安全等级哪家的云服务器便宜稳定性好点VMware宣布收购Octarine,云原生安全策略升级云服务器硬盘要根据业务类型选择ecs计算型云服务器我想从新安装网站原来的受到过攻击