在jQuery盛行的时代,我们最难避免的,就是回调地狱,尤其是当遇到了嵌套ajax请求的时候,我们的代码大都是这样的:

这样的代码,一是嵌套深了看起来难看,而是不利于调试,三是当一个回调里面的逻辑太多了的时候,一个方法的代码可能很长很长。于是,聪明的你肯定这样做过:

这样虽然看上去没上面那么糟糕了,但又却暴露出另外一个问题,各个ajax之间的依赖关系不是那么的明朗,对于维护代码的人而言,后期维护是个很痛苦的事情。

后来随着Promise的出现,我们的回调地狱稍微有了改观,但还是不够优雅:

但如果用了 async 和 await,代码看起来就更加清晰明朗了:

接下来,我们就来谈谈async 和 await 的 api 和 使用注意事项。

 

一、async

1、async 函数返回的是一个 Promise 对象,如果结果是值,会经过 Promise 包装返回。
2、async 函数中,如果有多个 await 关键字时,如果有一个 await 的状态变成了 rejected,那么后面的操作都不会继续执行。
3、如果在一个 async 方法中,有多个 await 操作的时候,程序会变成完全的串行操作,后一个会一直等到前一个执行完成才会执行。如果你的业务场景是多个异步操作之间不存在结果的依赖关系,请使用 promise.all。
 
async 函数声明

 

二、await

1、await 只能存在与 async 方法内部,在其他地方不行。
2、await 只能在 async 函数的当前作用域下执行,不能跨层级使用。
3、await 命令后面可以是 Promise 对象或值,如果是值,会自动转成一个立即 resolve 的 Promise 对象。
4、await 的返回结果是它后面所跟的 promise 的执行的结果,可能是 resolved 或者 rejected 的值。
 
# 注意点
1. 对于下面这段代码,打印结果并不是你期望的返回值 1,而是一个 promise。

因为 async 方法返回的永远是一个 promise,即使开发者返回的是一个常量,也会被自动调用 promise.resolve 方法转换为一个 promise。因此对于这种情况,上层调用方法也需要是 async 函数,所以你得像下面这样才能得到想要的结果:

2. 如下代码也会报错,因为 await 和 async 中间跨了一层作用域。

 

# 思考

请看下面代码,并猜想一下执行结果的打印顺序。

正确结果如下,你的思考正确了吗?

简单分析一下:
因为我们一开始调用了 fn1,所以第一个打印 fn1 start 毋庸置疑,但因为要等待 fn2 执行完,所以不会马上执行后面的代码;
接着 fn1 又调用了 fn2,所以会立即执行 fn2,打印了fn2 start,同 fn1 一样,由于要等待 fn3,所以不会马上执行后面的代码;
接着 fn2 又调用了 fn3,所以打印了fn3
这个时候,已经没有异步代码了,直接执行最后一行代码,打印 over
最后一行代码执行过后,已经没有同步代码了,所以开始等待异步执行;
这个时候,不防先思考一下我们的异步队列有哪些,分别是两次通过 await 添加的 fn2 和 fn3;
所以在 console.log(‘fn1 end’) 执行之前,要等待 fn2,因为 fn3 里已经没有异步了,所以直接打印 fn2 end
最后等到 fn2 执行完成,直接打印 fn1 end
其实,就上面的示例而言,抛开最后一行代码,你会发现,这跟 koa 的中间件差不多是一个逻辑。这种剥洋葱的模型在前端很多,最典型的就是 dom 事件的捕捉与冒泡。
 

# 实用场景

上文提到的ajax嵌套就是一个典型的例子。

再比如,react 的 setState 方法,很多人是像这样使用的:

用了 async 和 await 之后,就是下面这样:

还有,当我们使用 mysql 时,可以这样封装我们的查询方法:

使用的时候就可以用 async 和 await 了:

OK,本文到此结束。