一次性了解 JavaScript 异步编程
本文还是从经典三问(是什么为什么怎么用)来浅析JavaScript异步编程。
什么是异步
有异步肯定就有同步,那我们就比较的来看。
1、同步:同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;
2、异步:异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
可能这样讲还是比较晦涩难懂,那我打个比喻,比如你要邀请你的女神去吃饭,然后你等她回应之后你们一起去吃饭(如果女神不回应就不去吃饭了),这个过程就是同步;
但是如果你邀请了你的女神之后,你不等她回应自己先去吃饭了,不管她到底接受不接受邀请,先吃了饭再说,那么这就是异步。
为什么要用异步编程
相信大家也清楚,JavaScript是单线程的,所以也就是说,同步代码很有可能会阻塞任务。如果我们使用异步编程,就可以避免阻塞后面的代码的执行。
怎么用异步编程
现在的异步编程方案主要分为四种,接下来我们一个一个的来看。
一、回调函数
用最简单的回调函数来做例子:
function f1(callback) {
setTimeout(function() { // f1的任务代码
callback();
}, 1000);
}
f1(f2)
通过setTimeout callback函数把同步操作变成了异步。
回调函数的优点便是:简单、容易理解、优化了程序执行速度;
缺点也很明显:不便于代码阅读和理解、各个部分高度耦合,而且每个任务只能指定一个回调函数。
更不方便的是,如果某个业务,依赖于上层业务的数据,上层业务又依赖于更上一层的数据,我们还采用回调的方式来处理异步的话,就会出现回调地狱。
二、Promise
Promise在一定程度上解决了回调地狱的问题,Promise在一定程度上其实改善了回调函数的书写方式,无论有再多的业务依赖,通过多个then(...)来获取数据,让代码只在纵向进行扩展;
另外一点就是逻辑性更明显了,将异步业务提取成单个函数,整个流程可以看到是一步步向下执行的,依赖层级也很清晰,最后需要的数据是在整个代码的最后一步获得。(当然前提是实现的Promise完全遵循PromiseA+规范)
const p1 = Promise.resolve(1)
const p2 = Promise.resolve(Promise.resolve(3))
const p3 = Promise.resolve(Promise.reject(4))
p1.then(value => {
console.log('p1', value)
})
p2.then(value => {
console.log('p2', value)
})
p3.catch(reason => {
console.log('p3', reason)
})
三、Generator
生成器Generator是ES6中定义的,它是一种顺序、看似同步的异步流程控制表达风格。
在这之前我们先看看两个ES6新增的协议:可迭代协议和迭代器协议
1、可迭代协议(for of循环)
允许对象定义他的可迭代行为,比如在for of结构中,哪些值可以遍历到。在js中的某些类型是内置好的可迭代对象,比如:字符串、数组、类型数组、Map对象、Get对象等。而Object类型不可迭代。这些内置可迭代对象可以进行迭代的原因是内部实现了@@iterator 方法,即在该对象或该对象的原型链上有Symbol.iterator属性实现了@@iterator 方法。
2、迭代器协议:(Iterator对象)
定义了产生一系列值的标准方式。一个对象必须实现next()方法,才能成为迭代器。 next() 方法必须返回一个对象,该对象应当有两个属性: done 和 value done(boolean)如果迭代器可以产生序列中的下一个值,则为 false。 value 迭代器返回的任何 JavaScript 值
3、用Generator实现异步
const fs = require('fs');
const co = require('co');
const bluebird = require('bluebird');
const readFile = bluebird.promisify(fs.readFile);
function* read() {
yield readFile(A, 'utf-8');
yield readFile(B, 'utf-8');
yield readFile(C, 'utf-8');
//....
}
co(read()).then(data => {
//code
}).catch(err => {
//code
});
也就是说,Generator生成器解决了Promise没有解决的纵向扩展。
四、Async/Await
Async/Await是ES7新引入的概念。他就是结合了上面所说的Promise和Generator,用同步的方式实现异步操作。
我们简单看一段使用async/await的代码:
async function fn3() {
try {
const value = await fn2() // await右侧表达式为promise,得到结果promise成功的value
console.log('value', value);
} catch (error) {
console.log('失败的结果', error)
}
}
fn3()
可以看出,用的同步的书写方式,只需要把异步的东西用Promise封装出去,然后使用await调用就可以了,也不需要像Generator一样需要手动控制next()执行。
目前来说,Async/Await是最好的异步编程方案了。
五、总结
这四种异步编程方案,其实是随着时间和技术的更新,慢慢迭代的。
也就是说,JS的异步发展史,可以认为是从 callback -> promise -> generator -> async/await。
我们可以从这四种编程方案更好的来理解JavaScript异步编程。
参考文章
JavaScript异步编程
细说JS异步发展历程
推荐阅读
-
JavaScript] 2 万字!一次性了解 Ajax、Fetch 和 Axios 的区别~(上)
-
一次性了解 JavaScript 异步编程
-
趣谈留言队列,搞清楚留言队列到底是什么!-说到消息队列,洪觉大概能猜到人们听到消息队列的反应,大致可以分为以下几类人。 第一类人,懵懵懂懂,刚上大学接触编程,还没用过消息队列,甚至还以为消息队列就是代码里面要新建一个List之类的;第二类人,听过消息队列,了解消息队列,但具体是什么还不是太明白,只知道一说到消息队列,脑海里马上出现了三组词,削峰、异步、解耦;第三类人,用过消息队列,对它有一定了解,但不知道为什么要这样设计,消息队列有什么样的前世今生,是如何演化到现在的模式的?**第四类人,已经对消息队列有了足够的了解,可以阅读本帖作为复习和温习。**你属于哪一类?无论你对消息队列了解多少,读完这篇文章后,我相信你都会有所收获。 什么是消息队列?我们为什么要使用消息队列?真的只是因为它看起来很勉强、很常用吗?当然不是,一项技术的出现往往是为了解决某种痛点,我们就从这个痛点出发,看看消息队列到底是为了解决什么问题而诞生的。 相信大家在工作之前,或者工作中接触单片机的次数会多一点,不管什么业务都一股脑塞进一个系统里,这种情况下接触消息队列的场景会比较少。但随着业务的增长,量上去了,单机系统就很难维护了,也扛不住并发量的增长,就需要把原来的单体应用拆分成多个服务。例如,牛奇网采用分布式架构,将原来的单体系统拆分成用户服务、题库服务、求职服务、论坛服务等,每个分布式节点都有一个集群,保证高可用性。 那虽然在这样的微服务架构下,如果某个核心业务并发量过大,系统就扛不住了。比如淘宝、淘票票、拼多多、京东等电商场景中的支付场景,你在某宝下单并支付后,调用支付服务,完成支付后,还需要更新订单的状态,这个时候就需要调用订单服务,那我们平时也下单,除了简单完成这些操作外,还会给你相应的积分;商家也会收到订单消息,并给您发送旺旺消息,确认订单无误;同时,也会给您发送消息,确认订单无误。确认订单无误;同时您还可以查看您的物流状态;还有系统为了给您推荐更适合您的商品,会根据您的订单做类似的推荐等等,我说的这些都是当我们下单后,肉眼可以感知到系统所做的动作。 **一个支付动作如果还需要调用那么多服务,等他们响应成功,最后再告诉用户你支付成功了,用户在系统中的整个体验会非常糟糕。**设想一下,假设请求服务+处理请求+响应总共需要 50ms,我们上面列出的场景:支付服务、订单服务、积分服务、商家服务、物流服务、推荐服务,总共需要 300ms。
-
深入了解 Java 异步编程:全面比较 Future 和 CompletableFuture
-
深入了解 Python 并发编程:解析多线程、多处理和异步编程