问题背景
有个做NodeJS后台的同学抛出了一段代码, 问为啥输出的结果不一样
1 | co(fucntion* () { |
问题分析
首先, 这两个方法想要做的都是同步的执行三个Promise.resolve()的方法, 输出 [1, 2, 3]的结果, 但是 async 的版本貌似有点不对劲
await后面的跟一个Promise对象, 返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。
在上述例子中, 显示await后面跟的是一Array表达式, 会直接返回一个 [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]的数组
而只co中, yield关键词后面可以跟 Promise, 数组, 对象, thunk函数, generator等对象
这这个情景中, yield后面是一个数组, co内部会把数组转换为一个promise, 最后调用Promise.al()等所有的promise执行完成后的再resolve结果.
这就解释清楚了为啥两个方法返回执行结果不一样
当时一看这个代码, 下意识觉得这个问题很明显是没有理解清楚 async语法 和 co库之间的差异
下面来系统的归纳一下generator, co, aysnc/await的知识, 当做复习回顾
从问题中延伸出的知识点
如果不了解 generator和Promise推荐阅读下面的文章来做知识准备
写这篇文章之前也写过一篇文章提到过co这个库
浅谈 Web 前端中间件两种模型与实现
koa的中间件模型就是基于co来实现的, 具体的可以阅读上面的文章了解
Generator:
一个普通的Generator使用方式如下:
yield
需要我们手动触发next方法来执行generator, 其中next()方法返回的当前yield 后面表达式的返回值对象
1 | var gen = function* (){ |
yield*
yield 后面可以跟一个 iterable object 作为参数,然后去执行这个迭代器,同时 yield 本身这个表达式的值就是迭代器迭代完成时(done: true)的返回值。
1 | var iterableFunc = function* () { |
co:
co的 yield 关键字后面可以跟的类型有: 普通的promise对象, 数组, 对象, thunk函数, generator(iterable object), 比如下面的几个例子:
1 | var iterableFunc = function* () { |
在co的源码中有一个 toPromise的方法来实现上面那些魔法封装
对于 yield*的处理, 可以看到 是返回 co.call(this, obj), 而co本身返回的就是一个promise
1 | function toPromise(obj) { |
async/await:
一个标准的async wait的使用场景
1 | aysnc asyncTask1 () { |
async 是generator的语法糖升级版本:
- 内置执行器, 普通的generator需要手动调用next()方法触发, co库就是帮我们自动执行的generator, 而async自动执行
- async await 更加符合编程语言的语义, async 代表这个函数里面是异步操作的, await 表示 要等到后面的表达式执行完之后才会继续执行后续代码
- aysnc 函数返回的是一个Promise对象, 函数最后return的结果就是 resolve的结果, 函数中throw error, 会reject到上层
- 更好的异常处理, 在回调中无法捕获的异常, 在Promise中有最后的catch可以捕获异常,而在async函数中, 可以直接使用try catch捕获异常,可以说是非常优雅了 (macrotask的异常依旧无法直接捕捉, 需要手动在macrotask函数中捕获并通过reject抛出), 总之我们可以在业务场景中做统一的异常捕获,使用装饰器专门捕获异常, 把异常都交给errorHandler处理
现在我们已经在我们的NodeJS项目中放心大胆的的使用async await了