Node异步解决方案-co与async

问题背景

有个做NodeJS后台的同学抛出了一段代码, 问为啥输出的结果不一样

1
2
3
4
5
6
7
8
9
10
co(fucntion* () {
var a = yield [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]
console.log(a)
// [1, 2, 3]
})
aysnc fucntion() {
var a = await [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]
console.log(a)
// [Promise.resolve(1), Promise.resolve(2). Promise.resolve(3)]
}

问题分析

首先, 这两个方法想要做的都是同步的执行三个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推荐阅读下面的文章来做知识准备

  1. Generator
  2. Promise

写这篇文章之前也写过一篇文章提到过co这个库
浅谈 Web 前端中间件两种模型与实现

koa的中间件模型就是基于co来实现的, 具体的可以阅读上面的文章了解

Generator:

一个普通的Generator使用方式如下:

yield

需要我们手动触发next方法来执行generator, 其中next()方法返回的当前yield 后面表达式的返回值对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var gen = function* (){
var promise1 = yield 1
var promise2 = yield 2
console.log(promise1);
console.log(promise2);
return ‘end’
};
var it = gen()
it.next().value
// 1
it.next().value
// 2
it.next()
// { value: ‘end’, done: true }

yield*

yield 后面可以跟一个 iterable object 作为参数,然后去执行这个迭代器,同时 yield 本身这个表达式的值就是迭代器迭代完成时(done: true)的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var iterableFunc = function* () {
yield 'iterableFunc_1'
yield 'iterableFunc_2'
return 'end iterableFunc'
}
var gen = function* (){
var promise1 = yield 1
var iterableObject = yield* iterableFunc()
var promise2 = yield 2
console.log(promise1)
console.log(iterableObject)
console.log(promise2)
};
var it = gen()
> it.next()
{ value: 1, done: false }
> it.next()
{ value: 'iterableFunc_1', done: false }
> it.next()
{ value: 'iterableFunc_2', done: false }
> it.next()
{ value: 2, done: false }

co:

co的 yield 关键字后面可以跟的类型有: 普通的promise对象, 数组, 对象, thunk函数, generator(iterable object), 比如下面的几个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var iterableFunc = function* () {
yield 'iterableFunc_1'
yield 'iterableFunc_2'
return 'end iterableFunc'
}
co(function* () {
var a = yield Promise.resolve(1)
console.log(a) // 1
var b = yield Promise.resolve(2)
console.log(b) // 2
var c = yield Promise.resolve(3)
console.log(c) // 3
var d = yield [Promise.resolve(4), Promise.resolve(5),Promise.resolve(6)]
console.log(d) // [4, 5, 6]
var e = yield iterableFunc()
console.log(e) // end iterableFunc
})

在co的源码中有一个 toPromise的方法来实现上面那些魔法封装
对于 yield*的处理, 可以看到 是返回 co.call(this, obj), 而co本身返回的就是一个promise

1
2
3
4
5
6
7
8
9
function toPromise(obj) {
if (!obj) return obj;
if (isPromise(obj)) return obj;
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
if ('function' == typeof obj) return thunkToPromise.call(this, obj);
if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
if (isObject(obj)) return objectToPromise.call(this, obj);
return obj;
}

async/await:

一个标准的async wait的使用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
aysnc asyncTask1 () {
return await Promise.resolve(1)
}
async aysncTask2() {
return await Promise.resolve(2)
}
async aysncErrorTsak() {
throw new Error(‘this is a Error’)
}
aysnc aysncWork() {
try { // 最好要用try包裹一下代码,也可以写个装饰器统一管理异常捕捉
await asyncTask1()
await asyncTask2()
await aysncErrorTsak()
}catch(e) {
console.log(e)
}
}

async 是generator的语法糖升级版本:

  1. 内置执行器, 普通的generator需要手动调用next()方法触发, co库就是帮我们自动执行的generator, 而async自动执行
  2. async await 更加符合编程语言的语义, async 代表这个函数里面是异步操作的, await 表示 要等到后面的表达式执行完之后才会继续执行后续代码
  3. aysnc 函数返回的是一个Promise对象, 函数最后return的结果就是 resolve的结果, 函数中throw error, 会reject到上层
  4. 更好的异常处理, 在回调中无法捕获的异常, 在Promise中有最后的catch可以捕获异常,而在async函数中, 可以直接使用try catch捕获异常,可以说是非常优雅了 (macrotask的异常依旧无法直接捕捉, 需要手动在macrotask函数中捕获并通过reject抛出), 总之我们可以在业务场景中做统一的异常捕获,使用装饰器专门捕获异常, 把异常都交给errorHandler处理

现在我们已经在我们的NodeJS项目中放心大胆的的使用async await了