一篇文章了解前端异步编程方案演变

对于JS而言,异步编程咱们能够采用回调函数,事件监听,发布订阅等方案,在ES6以后,又新添了Promise,Genertor,Async/Await的方案。本文将阐述从回调函数到Async/Await的演变历史,以及它们之间的关系。

1. 异步编程的演变

首先假设要渲染一个页面,只能异步的串行请求A,B,C,而后才能拿到页面的数据并请求页面

针对于不一样的异步编程方式,咱们会获得以下的代码:html

1.1 回调函数

// 假设request是一个异步函数
request(A, function () {
    request(B, function () {
        request(C, function () {
            // 渲染页面
        })
    })
})

回调函数的嵌套是愈发深刻的。在不断的回调中,request(A)回调函数中的其余逻辑会影响到request(B),request(C)中的逻辑,同理,request(B)中的其余逻辑也会影响到request(C)。在这个例子中,request(A)调用request(B),request(B)调用request(C),request(C)执行完毕返回,request(B)执行完毕返回,request(A)执行完毕返回。咱们很快会对前后顺序产生混乱,从而很难直观的分析出异步回调的结果。这就被称为回调地狱。编程

为了解决这种状况,ES6新增了Promise对象。segmentfault

1.2 Promise

// 假设request是一个Promise函数
request(A).then(function () {
    return request(B)
}).then(function () {
    return request(C)
}).then(function () {
    // 渲染页面
})

Promise对象用then函数来指定回调。因此,以前在1.1中回调函数的例子能够改成上文中的模样。能够看到,Promise并无消除回调地狱,可是却经过then链将代码逻辑变得更加清晰了。在这个例子中,request(A)调用request(B),request(B)调用request(C),request(C)执行完毕返回。如今,request(A)中的内容只能经过显示声明的data来影响到request(C)——若是没有显示的在回调中声明,则影响不了request(C),换言之,每段回调被近乎独立的分割了。异步

可是Promise自己仍是有一堆的then,仍是不能让咱们像写同步代码同样写异步的代码,所以JS又引入了Generator。async

1.3 Generator

function* gen(){
    var r1 = yield request(A)
    var r2 = yield request(B)
    var r3 = yield request(C)
    // 渲染页面
};

Generator是协程在ES6上的实现,协程是指一个线程上不一样函数间执行权能够相互切换。如本例,先执行gen(),而后在遇到yield时暂停,执行权交给request(A),等到调用了next()方法,再将执行权还给gen()。异步编程

经过协程,JS就实现了用同步的方式写异步的代码,可是Generator的使用要配合执行器,这天然是麻烦的。因而就有了Async/Await。函数

Generator的自动执行器是co函数库,有兴趣的同窗能够经过阅读《 co 函数库的含义和用法》来进行了解。

1.4 Async/Await

async function gen() {
    var r1 = await request(A)
    var r2 = await request(B)
    var r3 = await request(C)
    // 渲染页面
}

若是比较代码的话,1.4的代码只是把1.3的代码中* => async,yield变为await。但Async函数的实现,就是将 Generator函数和自动执行器,包装在一个函数里[1]。spawn就是自动执行器。spa

async function fn(args){
  // ...
}

// 等同于

function fn(args){ 
  return spawn(function*() {
    // ...
  }); 
}

除此之外,Async函数比Generator函数有更好的延展性——yield接的是Promise函数/Thunk函数,但await还能够包括普通函数。对于普通函数,await表达式的运算结果就是它等到的东西。不然若await等到的是一个Promise函数,await就会协程到这个Promise函数上,直到它resolve或者reject,而后再协程回主函数上[2]。固然,Async函数也比Generator函数更加易读和易理解。线程

2. 总结

本文阐述了从回调函数到Async/Await的演变历史。Async函数做为换一个终极解决方案,尽管在并行异步处理上还要借助Promise.all(),但其余方面已经足够完美。code

参考文档

  1. 深刻掌握 ECMAScript 6 异步编程》系列
  2. 理解JavaScript的 async/await
相关文章
相关标签/搜索