浅谈async、await 实现原理

前言:async、await 语法是ES6新出的,主要是为了解决多个Promise函数产生的嵌套层级过多的问题。

例:你有abcde...N个异步操作。每个异步操作又是依赖于上一个的异步函数(Promise)的相应结果(.then)来执行的。那么你的代码大概是这样的:

const initData = 1;
funtcion init(data){
    a(data).then((res)=>{
        b(res.data).then((res)=>{
            C(res.data).then((res)=>{
                d(res.data).then((res)=>{
                    ... // 一直嵌套下去
                })
            })
        })
    })
}
init(initData)

这样的代码不仅逻辑复杂还难以阅读,很容易产生bug。所以es6就出了个async、await 语法去解决这个问题,让代码变得简单易懂,容易维护:

const initData = 1;
function async init(data){
    let res = await a(data);
    res = await b(res.data);
    res = await c(res.data);
    res = await d(res.data);
    ......
}
init(initData)

 在理解async await 之前需要去了解 ES6的Generator,因为async await是基于Generator实现的代码中断操作(也就是上一个await处理完时代码不会继续往下执行,所以看起来就是中断了代码)

1、Generator:

Generator和普通的function 函数区别在于function * 之后有个*来告诉js这是Generator函数,然后代码内部由yield (可以理解为代码分割)把代码分割成若干个片段。通过Generator执行后的返回值.next()去一个个按顺序执行。看下面例子会变得比较容易理解:

function * init(){
    yield '第1次next,来执行的代码片段';
    yield '第2次next,来执行的代码片段';
    yield '第3次next,来执行的代码片段';
}

const f = init();
f.next() // {value: '第1次next,来执行的代码片段', done: false}
f.next() // {value: '第2次next,来执行的代码片段', done: false}
f.next() // {value: '第3次next,来执行的代码片段', done: false}
f.next() // {value: undefined, done: true}
f.next() // {value: undefined, done: true}
f.next() // {value: undefined, done: true}
.... // 接下来f.next() 的返回值都是{value: undefined, done: true},done:true表示代码片段已经执行完毕了,也就是执行到最后一个yield 。

// 上面的逻辑也理解为:init函数根据yield 标识把代码分割成了,

function next1(){
    return {value: '第1次next,来执行的代码片段', done: false}
}
function next2(){
    return {value: '第2次next,来执行的代码片段', done: false}
}
function next3(){
    return {value: '第3次next,来执行的代码片段', done: false}
}
function nextEnd(){
    return {value: undefined, done: true}
}
// 然后.next 一个个执行。

继续深入:next 函数也是可以接受参数的。他的参数就是yield 函数的返回值,通过下面例子来理解:

function * init(){
    const res1 = yield '第1次next,来执行的代码片段';
    console.log(res1,'res1')
    const res2 = yield '第2次next,来执行的代码片段';
    console.log(res2,'res2')
    const res3 = yield '第3次next,来执行的代码片段';
    console.log(res3,'res3')
};
const f = init();
f.next()
// f.next()的返回值是 {value:'第1次next,来执行的代码片段',done:false};
// 不会打印,还没执行到第一个console.log

f.next()
// f.next()的返回值是 {value:'第2次next,来执行的代码片段',done:false};
// 第1个console.log的是 undefined 'res1'

f.next()
// f.next()的返回值是 {value:'第3次next,来执行的代码片段',done:false};
// 第2个console.log的是 undefined 'res2'

f.next()
// f.next()的返回值是 {value:undefined ,done:true};
// 第3个console.log的是 undefined 'res3'

f.next()
// f.next()的返回值是 {value:undefined ,done:true};
// 没有东西打印

必须要注意的是,f.next() 返回值是 当前执行的yield 后面代码的值,不要return,自动返回({value:'第1次next,来执行的代码片段',done:false});而 const res1 = yield '第1次next,来执行的代码片段'; res的返回值是.next 传入的形参。但是值得注意的是:第一次执行.next,不需要传入参数,即使传入了也是无效,丝毫不会影响代码逻辑。例:


function * init(){
    const res1 = yield '第1次next,来执行的代码片段';
    console.log(res1,'res1')
    const res2 = yield '第2次next,来执行的代码片段';
    console.log(res2,'res2')
    const res3 = yield '第3次next,来执行的代码片段';
    console.log(res3,'res3')
};
const f = init();
f.next('init')
// f.next()的返回值是 {value:'第1次next,来执行的代码片段',done:false};
// 不会打印,还没执行到第一个console.log

f.next('init1')
// f.next()的返回值是 {value:'第2次next,来执行的代码片段',done:false};
// 第1个console.log的是 'init' 'res1' ,因为第一次执行.next。代码并没有执行到'const res1 = // ',这个阶段,所以你给next的传参肯定也是无效的。

f.next('init2')
// f.next()的返回值是 {value:'第3次next,来执行的代码片段',done:false};
// 第2个console.log的是 'init2' 'res2' ,第二次执行。代码已经执行到了'const res1 = '这个阶段, 
// 然后会看见'const res1 = ',是接受上一个yield 的返回值。那么这个时候它就被.next入参修改了。

f.next('init3')
// f.next()的返回值是 {value:undefined ,done:true};
// 第3个console.log的是 'init3' 'res3'

f.next('init4')
// f.next()的返回值是 {value:undefined ,done:true};
// 没有打印东西,代码执行完毕了。

 红色:第一次next;绿色:第二次next;蓝色:第三次next;橙色:第四次next。

2、实现async await

1.1、先用setTimeout 实现一个异步函数:awaitFn

function awaitFn(backData){
    return function (callBack){
        setTimeout(()=>{callBack(++backData)},1000)
    }
}

1.2、再用Generator实现一个asyncFn函数,模拟async await代码结构:

function* asyncFn(){
    let res = yield awaitFn(0);
    console.log(res,'第1次')
    res = yield  awaitFn(res);
    console.log(res,'第2次')
    res = yield  awaitFn(res);
    console.log(res,'第3次')
    res = yield  awaitFn(res);
    console.log(res,'第4次')
}

1.3、最关键的一步,通过Generator函数的特性,写一个递归函数来实现async await:

function init(fn){
    const f = fn();
    function next(data){
        const res = f.next(data);
        if(res.done) return res.value;
        res.value((backData)=>{
            next(backData);
        })
    }
    next()
}
init(asyncFn) // 然后执行init,把之前的asyncFn当形参传入

结果:

 每秒打印一次,返回值是入参自增。

到了这里我想你应该知道接下来应该怎么改了吧?我们来改动awaitFn函数和init:

awaitFn:

function awaitFn(backData){
//    return function (callBack){
//        setTimeout(()=>{callBack(++backData)},1000)
//    }
    return new Promise((rj)=>{
        setTimeout(()=>{rj(++backData)},1000)
    })
}

init:

function init(fn){
    const f = fn();
    function next(data){
        const res = f.next(data);
        if(res.done) return res.value;
        Promise.resolve(res.value).then((backData)=>{
            next(backData);
        }) // 无论是不是Promise 函数都会以Promise 函数形式触发
    }
    next()
}
init(asyncFn) // 然后执行init,把之前的asyncFn当形参传入

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值