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

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

1. 异步编程的演变

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

针对于不同的异步编程方式,我们会得到如下的代码:

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对象。

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。

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就是自动执行器。

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(),但其他方面已经足够完美。

参考文档

  1. 深入掌握 ECMAScript 6 异步编程》系列
  2. 理解JavaScript的 async/await

汤姆C
tomczhang的专栏,我会在这个专栏中撰写我的思考,工作中遇到的问题和苦恼,每周一篇

职业提升三部曲:

4.5k 声望
792 粉丝
0 条评论
推荐阅读
项目经历准备篇——如何准备阿里巴巴P6/P7前端面试
在上次的校招文章之后,有很多同学问有没有社招相关的东西可以写一篇,现在它来了。比起校招,社招更加看重项目经历+项目经历反应的思考。本文针对的是想进入阿里的P6/P7同学,着重讲解了很多同学容易忽视的项目...

这是你的玩具车吗30阅读 3.6k评论 5

正则表达式实例
收集在业务中经常使用的正则表达式实例,方便以后进行查找,减少工作量。常用正则表达式实例1. 校验基本日期格式 {代码...} {代码...} 2. 校验密码强度密码的强度必须是包含大小写字母和数字的组合,不能使用特殊...

寒青56阅读 8.4k评论 11

JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...

jenemy48阅读 7k评论 12

从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...

乌柏木75阅读 7.1k评论 16

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs42阅读 6.8k评论 12

封面图
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...

乌柏木45阅读 8.5k评论 6

从零搭建 Node.js 企业级 Web 服务器(二):校验
校验就是对输入条件的约束,避免无效的输入引起异常。Web 系统的用户输入主要为编辑与提交各类表单,一方面校验要做在编辑表单字段与提交的时候,另一方面接收表单的接口也要做足校验行为,通过前后端共同控制输...

乌柏木35阅读 6.7k评论 10

职业提升三部曲:

4.5k 声望
792 粉丝
宣传栏