Implement async/await with generator/yield
async/await
不是魔法,它只是一个语法糖,底层与生成器generator
密不可分。
这里以学习为目的,简单用一个例子来模拟它们的行为,能够有一个直观的理解。
需要的前置知识,不再赘述:
创建一个异步加法函数
延迟1秒返回加法结果,方便实验。
function addAsync(x: number, y: number): Promise<number> {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(x + y)
}, 1000)
})
}
实现单个yield(模拟await)
function doAsync(g: () => Generator): void {
// 调用异步函数main,返回一个可迭代的iterator
const it = g()
// 调用next,运行到第一个yield之后暂停
const { value } = it.next()
// 调用next则让yield之后的代码继续运行,并将v传递给yield作为返回值
value.then(v => it.next(v))
}
function* main() {
const result: number = yield addAsync(5, 2)
console.log(result)
}
doAsync(main)
多个yield
迭代支持多个yield,并处理异步/同步/字面量,和错误处理。
异步函数执行器
简单的递归行为,结束一个promise之后,再允许生成器g
继续执行,直到所有的yield
执行完毕,即done
为真时结束运行。
此处对value(为yield后面表达式的值)需要进行判断,可能是一个promise,也可能直接就是一个字面量值。
function doAsync(g: () => Generator): void {
const it = g()
// 递归执行next,一个yield接一个。
function iterate(lastValue?: any): void {
const { value, done } = it.next(lastValue)
// 停止迭代
if (done) return
// 方法一:如果value是Promise,则等待Promise完成,否则套一层静态Promise,则可以保证value是thenable的
const promise = Promise.resolve(value)
promise.then(iterate)
// 方法二:手动判断
// isThenable(value) ? value.then(iterate) : iterate(value)
}
iterate()
}
// function isThenable(obj: any) {
// return obj && typeof obj.then === 'function'
// }
运行
记住,await后面不一定是异步行为,可能是一个字面量,可能是一个同步任务。所以doAsync
要做处理。
以下异步相当于 async
替换为 *
,await替换为 yield
。
function* main() {
const result1: number = yield 123
console.log(result1)
const result2: number = yield addAsync(5, 2)
console.log(result2)
const result3: number = yield addAsync(666, 21)
console.log(result3)
}
doAsync(main)
结果:
错误处理
如果promise是被reject捕获,则直接抛出一个错误。
怎么抛?
生成器函数(异步函数)返回值为it,那么就用it.throw(e)
抛。
const promise = Promise.resolve(value)
promise
.then(v => iterate(v))
.catch(err => it.throw(err))
剩下的太简单,懒得写了,就到这。
return处理
async函数的return结果,会被包装成一个Promise再返回出去。 那么需要做的就是:
- 返回一个
new Promise((resolve, reject) => { })
; - 在
it.done
为true
时,执行reslove(it.value)
,结束运行
; - 在执行代码过程中出现错误时 或 Promise被catch捕获,执行
reject(err)
或it.throw(err)
,结束运行
。