js回调地狱
前言
在做项目的过程中,凡是调用接口都使用了async await,只知道这是用来解决异步的,对其原理却一窍不通,特此写下这篇文章,从回调地狱说起,来揭秘async await的前世今生。
起源
首先要明确的是,js是一个单线程语言。为了避免个别耗时过多的任务造成阻塞,因而划分出了同步任务与异步任务,并且js中正常的函数执行是同步执行的(即按顺序依次执行)。典型的异步函数有setTimeout(), 如下 :
1 | setTimeout(function () { |
上面的这段函数的输出会先输出”second”,而不是按顺序执行,因为这里的setTimeout()为异步函数,程序执行到异步函数时,会先不进入主线程、而进入任务队列(task queue)的任务,在引擎认为此任务可以执行了(这里是等待一秒后),才会进入主线程执行。
这里的异步任务执行时间很明确,即在等待1秒后。但有些异步函数的具体执行结束时间是不能确定的,比如读取文件(花费时间和文件大小有关),比如调用接口(与后台性能甚至网速有关)。
如果说,我们需要异步任务的结果来干一些事,此时该如何确定什么时候能拿到异步任务的结果 ?
答案是用回调函数。回调函数 : 将函数作为参数传给另外一个函数。
下面以setTimeout()为异步函数为例。
1 | let a = 0,b = 1 |
如果有多个异步函数依次调用呢?就要写成这样。
1 | let a = 0,b = 1 |
为了完成 回调的回调,上面的代码就写成了“瀑布式”,代码冗余,缩进繁琐,难以维护,若有10个异步函数需要依次执行,就形成了 回调地狱。
总结一下,回调地狱就是为是实现代码顺序执行而出现的一种操作,但它会造成我们的代码可读性非常差,后期不好维护
Promise
Promise 是一个 ECMAScript 6 提供的类,目的是更加优雅地书写复杂的异步任务。下面是Promise对象:
1 | new Promise(function (resolve, reject) { |
首先可以看到Promise构造函数有两个参数:resloved和rejected,他们都是回调函数。
- resloved函数:异步操作成功后调用,并将异步操作的结果传递出去用, 用 .then(): 接收
- rejected函数:异步操作失败后调用,将失败的信息传递出去, 用.catch(): 接收
- Promise 状态发生改变,就会触发.then()里的响应函数处理后续步骤.
为了避免回调地狱,现在我们用 Promise 来实现同样的功能:
1 | new Promise(function (resolve, reject) { |
上述代码已经解决了回调地狱问题(将嵌套格式的代码变成了顺序格式的代码),为了更简洁的代码可以写成Promise函数和调用两个部分:
Promise函数:
1 | //这种返回值为一个 Promise 对象的函数称作 Promise 函数,它常常用于开发基于异步操作的库。 |
调用:
1 | //简洁多了 |
async await
想要继续优化上面的代码,就引出了异步函数(async function)。
ECMAScript 7提供了async/await来编写异步代码,是回调地狱的终极解决方案,使得异步代码看起来像同步代码,这正是它的魔力所在。。
对于上文编写的两个Promise函数add与multi,可以通过async await调用
1 | async function start(){ |
注意 :async/await是基于Promise实现的,它不能用于普通的回调函数。
由示例可知,使用Async/Await明显节约了不少代码。我们不需要写.then,不需要写匿名函数处理Promise的resolve值,也不需要定义多余的data变量,还避免了嵌套代码。这些小的优点会迅速累计起来,这在之后的代码示例中会更加明显。
实现你的async await
敬请期待 ….