专栏首页Czy‘s Blogasync/await剖析

async/await剖析

async/await剖析

JavaScript是单线程的,为了避免同步阻塞可能会带来的一些负面影响,引入了异步非阻塞机制,而对于异步执行的解决方案从最早的回调函数,到ES6Promise对象以及Generator函数,每次都有所改进,但是却又美中不足,他们都有额外的复杂性,都需要理解抽象的底层运行机制,直到在ES7中引入了async/await,他可以简化使用多个Promise时的同步行为,在编程的时候甚至都不需要关心这个操作是否为异步操作。

分析

首先使用async/await执行一组异步操作,并不需要回调嵌套也不需要写多个then方法,在使用上甚至觉得这本身就是一个同步操作,当然在正式使用上应该将await语句放置于 try...catch代码块中,因为await命令后面的Promise对象,运行结果可能是rejected

function promise(){
    return new Promise((resolve, reject) => {
       var rand = Math.random() * 2; 
       setTimeout(() => resolve(rand), 1000);
    });
}

async function asyncFunct(){
    var r1 = await promise();
    console.log(1, r1);
    var r2 = await promise();
    console.log(2, r2);
    var r3 = await promise();
    console.log(3, r3);
}

asyncFunct();

async/await实际上是Generator函数的语法糖,如Promises类似于结构化回调,async/await在实现上结合了Generator函数与Promise函数,下面使用Generator函数加Thunk函数的形式实现一个与上边相同的例子,可以看到只是将async替换成了*放置在函数右端,并将await替换成了yield,所以说async/await实际上是Generator函数的语法糖,此处唯一不同的地方在于实现了一个流程的自动管理函数run,而async/await内置了执行器,关于这个例子的实现下边会详述。对比来看,asyncawait,比起*yield,语义更清楚,async表示函数里有异步操作,await表示紧跟在后面的表达式需要等待结果。

function thunkFunct(index){
    return function f(funct){
        var rand = Math.random() * 2;
        setTimeout(() => funct(rand), 1000)
    }
}

function* generator(){ 
    var r1 = yield thunkFunct();
    console.log(1, r1);
    var r2 = yield thunkFunct();
    console.log(2, r2);
    var r3 = yield thunkFunct();
    console.log(3, r3);
}

function run(generator){
    var g = generator();

    var next = function(data){
        var res = g.next(data);
        if(res.done) return ;
        // console.log(res.value);
        res.value(next);
    }

    next();
}

run(generator);

实现

async函数内置了执行器,能够实现函数执行的自动流程管理,通过Generator yield ThunkGenerator yield Promise实现一个自动流程管理,只需要编写Generator函数以及Thunk函数或者Promise对象并传入自执行函数,就可以实现类似于async/await的效果。

Generator yield Thunk

自动流程管理run函数,首先需要知道在调用next()方法时,如果传入了参数,那么这个参数会传给上一条执行的yield语句左边的变量,在这个函数中,第一次执行next时并未传递参数,而且在第一个yield上边也并不存在接收变量的语句,无需传递参数,接下来就是判断是否执行完这个生成器函数,在这里并没有执行完,那么将自定义的next函数传入res.value中,这里需要注意res.value是一个函数,可以在下边的例子中将注释的那一行执行,然后就可以看到这个值是f(funct){...},此时我们将自定义的next函数传递后,就将next的执行权限交予了f这个函数,在这个函数执行完异步任务后,会执行回调函数,在这个回调函数中会触发生成器的下一个next方法,并且这个next方法是传递了参数的,上文提到传入参数后会将其传递给上一条执行的yield语句左边的变量,那么在这一次执行中会将这个参数值传递给r1,然后在继续执行next,不断往复,直到生成器函数结束运行,这样就实现了流程的自动管理。

function thunkFunct(index){
    return function f(funct){
        var rand = Math.random() * 2;
        setTimeout(() => funct(rand), 1000)
    }
}

function* generator(){ 
    var r1 = yield thunkFunct();
    console.log(1, r1);
    var r2 = yield thunkFunct();
    console.log(2, r2);
    var r3 = yield thunkFunct();
    console.log(3, r3);
}

function run(generator){
    var g = generator();

    var next = function(data){
        var res = g.next(data);
        if(res.done) return ;
        // console.log(res.value);
        res.value(next);
    }

    next();
}

run(generator);

Generator yield Promise

相对于使用Thunk函数来做流程自动管理,使用Promise来实现相对更加简单,Promise实例能够知道上一次回调什么时候执行,通过then方法启动下一个yield,不断继续执行,这样就实现了流程的自动管理。

function promise(){
    return new Promise((resolve,reject) => {
        var rand = Math.random() * 2;
        setTimeout( () => resolve(rand), 1000);
    })
}

function* generator(){ 
    var r1 = yield promise();
    console.log(1, r1);
    var r2 = yield promise();
    console.log(2, r2);
    var r3 = yield promise();
    console.log(3, r3);
}

function run(generator){
    var g = generator();

    var next = function(data){
        var res = g.next(data);
        if(res.done) return ;
        res.value.then(data => next(data));
    }

    next();
}

run(generator);
// 比较完整的流程自动管理函数
function promise(){
    return new Promise((resolve,reject) => {
        var rand = Math.random() * 2;
        setTimeout( () => resolve(rand), 1000);
    })
}

function* generator(){ 
    var r1 = yield promise();
    console.log(1, r1);
    var r2 = yield promise();
    console.log(2, r2);
    var r3 = yield promise();
    console.log(3, r3);
}

function run(generator){
    return new Promise((resolve, reject) => {
        var g = generator();
        
        var next = function(data){
            var res = null;
            try{
                res = g.next(data);
            }catch(e){
                return reject(e);
            }
            if(!res) return reject(null);
            if(res.done) return resolve(res.value);
            Promise.resolve(res.value).then(data => {
                next(data);
            },(e) => {
                throw new Error(e);
            });
        }
        
        next();
    })
   
}

run(generator).then( () => {
    console.log("Finish");
});

每日一题

https://github.com/WindrunnerMax/EveryDay

参考

https://segmentfault.com/a/1190000007535316
http://www.ruanyifeng.com/blog/2015/05/co.html
http://www.ruanyifeng.com/blog/2015/05/async.html

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JavaScript变量提升

    在JavaScript中变量声明与函数声明都会被提升到作用域顶部,优先级依次为:变量声明 函数声明 变量赋值

    WindrunnerMax
  • Js中Currying的应用

    柯里化Currying是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下的参数且返回结果的新函数的技术,是函数式编程应用。

    WindrunnerMax
  • Vue中数组变动监听

    Vue的通过数据劫持的方式实现数据的双向绑定,即使用Object.defineProperty()来实现对属性的劫持,但是Object.defineProper...

    WindrunnerMax
  • 性能优化三部曲之三——Node直出让你的网页秒开

    项目: 手Q群成员分布直出 原因: 为家校群业务直出做准备 群成员分布业务是小型业务,而且逻辑相当简单,方便做直出试验田 基本概念: 直出其实并不算是新概念。只...

    李成熙heyli
  • es6之深入理解promise对象

    连小壮
  • 深入理解es6的promise

    我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?inv...

    连小壮
  • JavaScript变量提升

    在JavaScript中变量声明与函数声明都会被提升到作用域顶部,优先级依次为:变量声明 函数声明 变量赋值

    WindrunnerMax
  • JS魔法堂:ES6新特性——GeneratorFunction介绍

    一、前言                                   第一次看koajs的示例时,发现该语句 function *(next){.....

    ^_^肥仔John
  • JavaScript解析机制之变量提升

    在当前作用域下,JS 运行之前,会把带有 var 和 function 关键字的事先声明,并在内存中安排好。(这个过程也可以理解为变量提升)然后再从上到下执行 ...

    Leophen
  • 前端必备,25个最基本的JavaScript面试问题及答案

    1.使用 typeof bar === "object" 来确定 bar 是否是对象的潜在陷阱是什么?如何避免这个陷阱?

    用户5997198

扫码关注云+社区

领取腾讯云代金券