async,await其实是基于generator和promise来实现的,具体这两个东西的原理就不展开来讲了, 可以参照阮一峰的文章来理解, 首先上一个简单的例子
let p1 = new Promise((resolve) => { setTimeout(() => { resolve('b') }, 1000) })
function *a(){
let res = yield p1 //拿到一个pending的promise
console.log(res)
}
基于这个例子,我们必须要打印的res在p1的状态改变之后再执行才能达到同步链式调用的目的
所以我们需要对generator函数做一层封装
function asyncTest(fn) {
let gen = fn()
function onFullfilled(value) {
let ret = gen.next(value)
if (ret.done) {
return
}
Promise.resolve(ret.value).then(onfullfilled) //这里对返回值进行Promise.resovle是
//为了防止返回的值不是promise
}
onfullfilled()
}
asyncTest(a) //运行代码
以上代码精简了一些判断处理,
可以看出我们对每段yield之前的代码(gen.next)都做了一层封装,
只有**当gen.next()跑完得到promise,**才会对返回值进行promise.then,这里可以试想,如果
函数yield前面的函数执行被卡死了,那么后面也不会继续gen.next了,这样才能达到我们顺序打印的目的
a = asyncTest(a)
function *main(){
let aPromise = a()
let res = yield aPromise //a函数被其他函数调用了,形成了一个递归的关系
console.log(res)
}
main = asyncTest(main)
main()
这时候的例子就长得很像我们工作中的例子了
流程分析: 建议对照下面的完整代码看
我们把main代码分成两块看,第一块是yield前面的,这时候a的执行的就是我们封装过的a,之所以
要封装是为了让第一段代码也就是a()的执行返回一个promise,这里的话就跟原声的async保持了一致,然后进入到a里面,这样看也就是跟第一个例子一摸一样,当promise执行完毕继续向下走,直到a方法结束,然后这时候第一段代码结束,返回了asyncTest里面的promise,这里是因为参照promsie的源码,promise传入的构造函数是**同步执行的,不执行完的话,这个promise是不会返回的,也就是完整代码里面的ret.value,**当next函数里面拿到ret.value,这时候ret.value状态肯定不是pending的.接着对他进行then,then的参数是onFullfilled,这样也就可以顺理成章的自动往下跑了.
main的第一段->a的第一段->a的第二段->main的第二段->结束
完整代码:
function asyncTest(fn) {
//这里相当于与给参数的generator函数套了一层
return function () {
return new Promise((resolve, reject) => {
let gen = fn()
function next(ret) {
if (ret.done) {
return resolve(ret.value)
}
return Promise.resolve(ret.value).then(onFullFilled, err => {
reject(err)
})
}
function onFullFilled(value) {
let ret
try {
ret = gen.next(value)
} catch (e) {
return reject(e)
}
next(ret)
}
onFullFilled()
})
}
}