Generator模拟实现async+await
# 简介
`ES6` 之前异步编程方法大概有4种,分别是回调函数、事件监听、发布订阅、`Promise` 对象。`ES6` 提供了 `Generator` 函数作为一种新的异步编程方案,`ES7` 中的 `async` 函数更是被作为异步编程的终极解决方案。 既然 `ES6` 已经提供了 `Generator` 函数来解决异步,为什么 `ES7` 又新增了 `async` 函数呢?其实 `async` 只是一个语法糖,其本质是 `Generator` 函数+执行器。
# 实现
当调用 `Generator` 函数时,会返回一个遍历器对象,代表 `Generator` 函数的内容指针。之后每次调用 `next` 方法时,就会返回一个有着 `value` 和 `done` 两个属性的对象。`value` 属性表示当前内部状态的值(对应`yield` 语句后的值),`done` 属性表示是否遍历结束。
基于 `Generator` 函数的特点,如果我们可以实现一个执行器,自动完成 `Generator` 函数的调用和遍历器对象的迭代执行,就可以实现类似 `async` 函数的效果。
以下是一个简单的 async 函数的代码,最终会输出 `AB`。
```javascript
async function asyncFun() {
let a = await Promise.resolve('A')
let b = await Promise.resolve('B')
return a + b
}
asyncFun().then(res => console.log(res)) // AB
```
使用 `Generator` 函数+执行器后,期望的效果如下:
```javascript
function* generatorFun() {
let a = yield Promise.resolve('A')
let b = yield Promise.resolve('B')
return a + b
}
// run 为执行函数
run(generatorFun).then(res => console.log(res)) // AB
```
`run` 函数的入参是 `Generator` 函数,返回值为 `Promise` 对象。核心逻辑是,迭代执行 `next` 函数,直到迭代完成(`done` 为 `true`)。简易实现为:
```javascript
function run(gen) {
return new Promise(function (resolve) {
// 执行Generator函数
let g = gen()
const next = (context) => {
let { done, value } = g.next(context)
if (done) {
// 完成返回
resolve(value)
} else {
// 未完成继续执行next函数,并传入本次执行结果
value.then(next)
}
}
next()
})
}
```
除了执行器之外,`async` 相比于 `Promise` 还有另一个特点,`Promise` 的异常一般通过 `catch` 函数来捕获,而 `async` 函数可以用 `try catch` 来捕获。而要实现这个效果,需要借助遍历器对象的 `throw` 方法。核心代码实现如下:
```javascript
function run(gen) {
return new Promise(function (resolve, reject) {
let g = gen()
const next = (context) => {
let res
try {
res = g.next(context)
} catch (e) {
return reject(e)
}
if (res.done) {
resolve(res.value)
} else {
res.value.then(next, err => {
let res
try {
// 借助迭代器的throw方法抛出异常,可悲 try catch 捕获
res = g.throw(err)
} catch (e) {
// 如果外层没有try catch,依然可以通过catch方法捕获
return reject(e)
}
next(res)
})
}
}
next()
})
}
function* generatorFun1() {
try {
let a = yield Promise.resolve('A')
let b = yield Promise.reject('B')
return a + b
} catch (e) {
console.error('try catch error', e)
}
}
// 输出try catch error
run(generatorFun1)
.then(res => console.log(res))
.catch(err => console.error('promise catch', err))
function* generatorFun2() {
let a = yield Promise.resolve('A')
let b = yield Promise.reject('B')
return a + b
}
// 输出promise catch
run(generatorFun2)
.then(res => console.log(res))
.catch(err => console.error('promise catch', err))
```
# co 模块
上文代码仅为简易的实现,主要是为了了解 `Generator` 实现 `async` 的核心原理,并没考虑参数类型以及执行结果非 `Promise` 对象等问题。完善的执行器代码可以参考 [co 模块](https://github.com/tj/co/blob/master/index.js)的实现。
> `co` 模块是著名程序员 `TJ Holowaychuk` 于 2013 年 6 月发布的一个小工具,用于 `Generator` 函数的自动执行。
```javascript
// co与run一样都为generator函数执行器
var co = require('co');
co(generatorFun).then(res => console.log(res)) // AB
```