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

async-await和generators有什么区别

时间:2023-04-03 13:42:22 Node.js

async/await是es2017的一个重要新特性。async/await和es2015发布的生成器有很多相似之处。stackoverflow上有很多关于两者区别的问题,也有一些很好的回答。如果你使用过co模块,基于生成器的代码看起来很像async/await。这是async/await处理HTTP请求三次。asyncfunctiontest(){letifor(i=0;i<3;++i){try{awaitsuperagent.get('http://google.com/this-throws-an-error')break}catch(err){}}console.log(i)//3}相同函数的生成器实现:consttest=co.wrap(function*(){letifor(i=0;i<3;++i){try{yieldsuperagent.get('http://bad.domain')break}catch(err){}}console.log(i)//3})通过观察,可以写一个async/awaitConvertertogenerators,原理是将asyncfunction(){}替换成co.wrap(function*(){}),并用yield替换await。那么两者有什么区别呢?重要的区别是从Node.js4.x开始就支持生成器,而async/await需要Node.js>=7.6.0。不过Node.js4.x早就不再维护了,Node.js6.x也将在2019年结束维护,所以这个区别现在已经不是那么重要了。另一个区别是co模块是开发者维护的第三方模块,而async/await是js语言的一部分。所以需要把co写到package.json中,async/await没有,但是如果要支持老浏览器,需要配置converter。堆栈跟踪得到不同的错误。async/await比生成器得到更清晰的错误。而且,由于async/await是JavaScript语言的核心部分,而不是像co这样的用户级库,因此未来可能会对async/await堆栈跟踪进行更多改进。下面是一个示例,显示异步函数抛出的错误。asyncfunctionrunAsync(){awaitnewPromise(resolve=>setTimeout(()=>resolve(),100))thrownewError('Oops!')}//Error:Oops!//在runAsync(/home/val/test.js:5:9)//在runAsync().catch(error=>console.error(error.stack))下面是generators实现的同一个函数,注意onFulfilled()和Generator.next()揭示了co模块的工作原理。constco=require('co')construnCo=co.wrap(function*(){yieldnewPromise(resolve=>setTimeout(()=>resolve(),100))thrownewError('Oops!')})//Error:Oops!//在D:\code\js\test\babel-test\src\co_test.js:5:9//atGenerator.next()//atonFulfilled(D:\code\js\test\babel-test\node_modules\co\index.js:65:19)runCo().catch(error=>console.error(error.stack))Thunks和Promisesconvertasync/await仅用于Promise,如果用于非Promise则无用。asyncfunctionrunAsync(){//res将是一个函数//语法需要括号,因为函数不是一个promiseconstres=await(cb=>cb(null,'test'))console.log(res)}runAsync().catch(error=>console.error(error.stack))另一方面,co将yield值转换为Promise。当你yield一个带有单个参数的函数时,即一个Node.js风格的回调,co将它变成一个promise。constco=require('co')construnCo=co.wrap(function*(){//`res`将是一个字符串,因为co将//`yield`的值转换为一个promise。`yieldcb=>{}`//模式被称为_thunk_.constres=yieldcb=>cb(null,'test')console.log(res)})runCo().catch(error=>console.error(error.stack))类似的,co也可以和Promise.all()有类似的效果。asyncfunctionrunAsync(){//在co中,你可以这样写//`yield[Promise.resolve('v1'),Promise.resolve('v2')]`constres=awaitPromise.all([Promise.resolve('v1'),Promise.resolve('v2');]);//'v1v2'console.log(res[0],res[1]);}第三方库的好处很多倍,Generators是async/await的超集。使用生成器,您可以使用它的一些强大功能来转换为您自己的异步/等待。Co内置的Promise转换只是冰山一角。例如,我曾经构建了一个返回可观察对象的co-like库。使用RxJS的过滤器运算符,处理错误非常容易。constcorx=require('./')require('rxjs/add/operator/filter')corx(function*(){yieldPromise.resolve('Test1')try{yieldPromise.reject('Test2')}catch(error){}console.log('Reachedtheend')}).filter(v=>!!v).subscribe(op$=>{//这将打印,即使错误被捕获,因为//这个回调在每个异步操作上执行!op$.subscribe(()=>{},err=>console.log('Erroroccurred',err))},error=>console.log('这不会打印'),()=>console.log('Done'))上面的杀手级功能是当订阅()时,生成器函数中发生的每个异步操作都会得到一个回调。这意味着您可以通过调试、分析、错误处理来检测每个单独的异步操作,而无需实际更改任何逻辑!这个特性很酷,但还不足以让我们放弃async/await并使用生成器。async/await的美妙之处在于它在大多数时间做你需要的事情,这个基于observable的库实际上会为你解决什么问题?为了进行调试,您将需要一种方法来从可观察的op$中提取有意义的信息,这在一般情况下是我从未发现过的。这就是为什么我重新审视中间件作为解决横切问题的正确工具的原因。此外,对于async/await用例,可观察对象可能不是一个好的选择,因为它们将被解释为多个值,甚至可能是值的无限循环。总结async/await和generators乍一看很相似,但两者之间有许多有意义的区别。async/await不需要第三方库,看起来更简洁;生成器通常与第三方库结合使用,但是这些第三方库提供了许多async/await所没有的强大功能。换句话说,异步/等待和生成器之间的权衡是简单性和灵活性之间的权衡。作为高级开发人员,在某些情况下您可以从开发人员那里获得有意义的价值,但大多数时候,async/await是更好的选择。