手写async await

1,335 阅读2分钟

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()
        })
    }
}