当前位置: 首页 > 后端技术 > Node.js

Node.js中的异步生成器和异步迭代

时间:2023-04-03 19:44:51 Node.js

作者:AlanStorm在引入async/await之前,这意味着在创建异步生成器(始终返回Promise并且可以等待的生成器)时,它引入了一些注意事项。今天,我们将了解异步生成器及其近亲异步迭代。注意:本文中的所有代码都是针对Node.js版本10、12和14开发和测试的,尽管这些概念应该适用于所有遵循现代规范的javascript。异步生成器函数看看这个小程序://File:main.jsconstcreateGenerator=function*(){yield'a'yield'b'yield'c'}constmain=()=>{constgenerator=createGenerator()for(constitemofgenerator){console.log(item)}}main()这段代码定义了一个生成器函数,使用这个函数创建一个生成器对象,然后使用for...of循环遍历生成器对象。非常标准的东西——尽管你在工作中从来没有真正使用过生成器来处理如此微不足道的事情。如果您不熟悉生成器和for...of循环,请参阅文章《Javascript 生成器》和《ES6 的循环和可迭代对象的》。在使用异步生成器之前,您需要对生成器和for...of循环有深入的了解。假设我们要在一个生成器函数中使用await,只要函数需要用async关键字声明,Node.js就支持这个特性。如果你对异步函数不熟悉,请看文章《在现代 JavaScript 中编写异步任务》。让我们修改程序并在生成器中使用await。//文件:main.jsconstcreateGenerator=asyncfunction*(){yieldawaitnewPromise((r)=>r('a'))yield'b'yield'c'}constmain=()=>{constgenerator=createGenerator()for(constitemofgenerator){console.log(item)}}main()同样在实践中,您不会这样做-您可能会等待来自第三方API或库的函数。为了让大家容易掌握,我们的例子尽量简单。如果你尝试运行上面的程序,你会遇到问题:$nodemain.js/Users/alanstorm/Desktop/main.js:9for(constitemofgenerator){^TypeError:generatorisnotiterableJavaScript告诉我们这个生成器是“不可迭代的”。乍一看,使生成器函数异步似乎也意味着它生成的生成器不可迭代。这有点令人困惑,因为生成器的目的是“以编程方式”生成可迭代对象。接下来搞清楚是怎么回事。检查生成器如果您阅读了Javascript生成器一文,您应该知道,如果一个对象定义了一个Symbol.iterator方法,并且该方法返回它,那么它是实现迭代器协议的javascript中的可迭代对象。当一个对象有一个next方法时,它实现了迭代器协议,并且next方法返回一个具有value属性、done属性或同时具有value和done属性的对象。如果使用下面的代码比较异步生成器函数返回的生成器对象和常规生成器函数://File:test-program.jsconstcreateGenerator=function*(){yield'a'yield'b'yield'c'}constcreateAsyncGenerator=asyncfunction*(){yieldawaitnewPromise((r)=>r('a'))yield'b'yield'c'}constmain=()=>{constgenerator=createGenerator()constasyncGenerator=createAsyncGenerator()console.log('generator:',generator[Symbol.iterator])console.log('asyncGenerator',asyncGenerator[Symbol.iterator])}main()会看到前者没有Symbol.迭代器方法,它有。$nodetest-program.jsgenerator:[Function:[Symbol.iterator]]asyncGeneratorundefined两个生成器对象都有一个next方法。如果您修改测试代码以调用此next方法://File:test-program.js/*...*/constmain=()=>{constgenerator=createGenerator()constasyncGenerator=createAsyncGenerator()console.log('generator:',generator.next())console.log('asyncGenerator',asyncGenerator.next())}main()会看到另一个问题:$nodetest-program.jsgenerator:{value:'a',done:false}asyncGeneratorPromise{}为了使对象可迭代,next方法需要返回一个具有value和done属性的对象。异步函数将始终返回一个Promise。此功能适用于使用异步函数创建的生成器——这些异步生成器将始终生成一个Promise对象。这种行为使得异步函数的生成器无法实现javascript迭代协议。幸运的是,异步迭代有办法解决这个矛盾。如果你查看异步生成器返回的构造函数或类//File:test-program.js/*...*/constmain=()=>{constgenerator=createGenerator()constasyncGenerator=createAsyncGenerator()console.log('asyncGenerator',asyncGenerator)}可以看出是一个类型或者类或者构造函数是AsyncGenerator而不是Generator的对象:asyncGeneratorObject[AsyncGenerator]{}虽然这个对象可能不是可迭代的,但它是一个异步可迭代的。对于异步可迭代的对象,它必须实现Symbol.asyncIterator方法。此方法必须返回一个实现迭代器协议的异步版本的对象。也就是说,对象必须有一个返回Promise的next方法,并且这个promise最终必须解析为一个具有done和value属性的对象。AsyncGenerator对象满足所有这些条件。这就留下了一个问题——我们如何迭代一个不可迭代但异步可迭代的对象?forawait...of循环仅使用生成器的next方法手动迭代异步可迭代对象。(注意这里的main函数现在是asyncmain-这允许我们在函数内部使用await)//File:main.jsconstcreateAsyncGenerator=asyncfunction*(){yieldawaitnewPromise((r)=>r('a'))yield'b'yield'c'}constmain=async()=>{constasyncGenerator=createAsyncGenerator()letresult={done:false}while(!result.done){result=awaitasyncGenerator.next()if(result.done){继续;}console.log(result.value)}}main()然而,这并不是最直接的循环机制。我不喜欢while循环条件,也不想手动检查result.done。此外,result.done变量必须同时存在于内部和外部块的范围内。幸运的是,大多数(也许是全部?)支持异步迭代器的javascript实现也支持特殊的forawait...of循环语法。例如:constcreateAsyncGenerator=asyncfunction*(){yieldawaitnewPromise((r)=>r('a'))yield'b'yield'c'}constmain=async()=>{constasyncGenerator=createAsyncGenerator()forawait(constitemofasyncGenerator){console.log(item)}}main()如果运行上面的代码,你会看到异步生成器和可迭代对象已经成功循环,并且在循环体中getPromise的完全解析值。$nodemain.jsabcforawait...of循环优先选择实现异步迭代器协议的对象。但是您可以使用它来迭代任何类型的可迭代对象。forawait(constitemof[1,2,3]){console.log(item)}当你使用forawait时,Node.js会首先在对象上寻找Symbol.asyncIterator方法。如果找不到,它将回退到使用Symbol.iterator。非线性代码执行与await一样,forawait循环将非线性代码执行引入到您的程序中。也就是说,您的代码将以与编写时不同的顺序运行。当您的程序第一次遇到forawait循环时,它将在您的对象上调用next。该对象将产生一个承诺,然后代码执行将离开您的异步函数,并且您的程序将继续在该函数之外执行。一旦您的承诺得到解决,代码执行将使用此值返回到循环体。当循环结束并进行下一次旅行时,Node.js将在对象上调用next。该调用产生另一个承诺,代码执行将再次离开您的函数。重复此模式,直到Promise解析为done为true的对象,然后在forawait循环之后继续执行代码。下面的例子可以说明一点:letcount=0constgetCount=()=>{count++return`${count}.`}constcreateAsyncGenerator=asyncfunction*(){console.log(getCount()+'进入createAsyncGenerator')console.log(getCount()+'abouttoyielda')yieldawaitnewPromise((r)=>r('a'))console.log(getCount()+'re-enteringcreateAsyncGenerator')console.log(getCount()+'abouttoyieldb')yield'b'console.log(getCount()+'re-enteringcreateAsyncGenerator')console.log(getCount()+'abouttoyieldc')yield'c'console.log(getCount()+'re-enteringcreateAsyncGenerator')console.log(getCount()+'exitingcreateAsyncGenerator')}constmain=async()=>{console.log(getCount()+'enteringmain')constasyncGenerator=createAsyncGenerator()console.log(getCount()+'开始等待循环')forawait(constasyncGenerator的项目){console.log(getCount()+'进入等待循环')console.log(getCount()+item)console.log(getCount()+'退出等待循环')}console.log(getCount()+'完成等待循环')console.log(getCount()+'leavingmain')}console.log(getCount()+'beforecallingmain')main()console.log(getCount()+'aftercallingmain')这个代码你使用编号的日志语句,它允许你跟踪它们的执行作为一个练习你需要自己运行程序然后看执行结果是什么样的。如果您不知道它是如何工作的,它可能会扰乱程序的执行,但异步迭代是一种强大的技术。