不得不使用 Aysnc/Await 的原因
目前 Async/Await 语法不论是在Node.js 端还是在浏览器端(依赖Babel)都支持的非常好。在和一些同学沟通中或者面试人中,也能听出他们对 Async/Await 有一定的了解,按理说他们写出来的代码应该都是或者大部分都用 Async/Await 替代 Promise 。但是,在后续的 Code Review 中,还是大量使用 Promise ,不停的 then then ... 充斥着嵌套地狱。Node.js 中,相对好点,大部分同学都在使用 Async/Await 这种方式 。
造成前后端使用上的区别,是受限于两者的开发思维。
对于后端开发者而言,代码执行的过程是同步的,写异步代码的情况比较少。如果使用 Promise.then 和 callback,反倒不容易理解和接受。比如数据库操作。早些年没有 Async 和 yield,直接用 callback ,代码是非常的晦涩,另外,在代码调试和定位方面,也是非常痛苦和棘手。
浏览器端情况就不同了,大家一开始接触的就是异步调用方式,现在需要变为「同步」的思维方式,反而不太自然了。
先从整体了解下它的特点。
Async/Await 简介
- 它是一种全新的写「异步调用」的方式,之前是回调函数或Promise。
- 它是基于 Promise 的同步语法糖。
- 类似 Promise ,也是非阻塞。
- 它的写法和行为看起来像同步代码,这也是它的魅力所在。
语法比较
假如某个页面展示之前,需要先请求数据,比如 fetchDetail ,它返回 Promise 对象,而这个 Promise 返回页面所需要的 JSON 数据。为了模拟调试过程,先简单把结果打印出来。
首先使用 Promise 的方式:
1// 省略部分Vue代码2methods: {3 fetchDetail() {4 // 调用接口数据5 return axios.get('<https://randomapi.com/api/b07f414474d47329b82840fba25dfabf>')6 .then(response => response.data)7 .then(response => response.results);8 },9},10mounted() {11 console.log('start mounted');12 this.fetchDetail()13 .then(products => {14 this.products = products;15 console.log('done', products);16 });17 console.log('end mounted');18},19// 结果会打印20// start mounted21// end mounted22// done [...]
现在,我们改成 Async / Await 语法
1// 省略部分Vue代码2async mounted() {3 console.log('start mounted');4 const products = await this.fetchDetail();5 this.products = products;6 console.log('done', products);7 console.log('end mounted');8},9// 结果会打印10// start mounted11// done [...]12// end mounted
可以看出以上两种方式的区别:
- 在 「mounted」方法之前有个 「async」关键词,且 「await」关键词必须在有 「async」函数的内部出现。含有「async」的函数,会隐式返回一个 promise 对象。
- 我们不能在顶层代码中直接使用 「await」调用,因为顶层代码没有「async」函数。这点在 Node.js 中会遇到。
1// app.js2// 这里不会执行,因为 app.js 没有 async 关键词3// const result = await fetchDetail();45// 如果需要执行只能用 then6fetchDetail().then(() => {7 // TODO8});
- 「await」函数是“同步”的,从打印结果中明显可以看出。
为什么 Async/Await 更好?
代码清晰简洁明了
大家想象下,如果有多个请求,且下一个请求都是从上一个请求中获取数据,如果使用 Promise ,就会一直 then 嵌套 then ,陷入到嵌套地狱中。单从代码的可读性和可维护性方面就会大打折扣。而使用 Async ,则不会出现嵌套的情况,大大提高了代码的可读性和可维护性。
错误捕捉
针对 Promise 捕捉错误,需要谨慎处理。因为 Promise 是异步的,try catch 不能写在最外层简单。 拿上面的 fetchDetail 举例。
1try {2 this.fetchDetail()3 .then(products => {4 this.products = products;5 // 转换错误6 const json = JSON.parse(undefined);7 this.json = json;8 console.log('done', products);9 })10 // 需要通过这种方式捕捉错误11 // .catch(() => {12 // console.info('error');13 // });;14} catch (error) {15 console.error(error);16}
在最外层直接 try catch ,是捕捉不到异常或错误,需要调用 Promise 中的 catch 才可以。
我们看看使用 async / await 如何捕捉错误。
1async mounted() {2 console.log('start mounted');3 try {4 const products = JSON.parse(await this.fetchDetail());5 this.products = products;6 console.log('done', products);7 } catch (err) {8 // 会执行到这里9 console.error(err);10 }11 console.log('end mounted');12},
可以看到,这种捕捉方式,比较清晰简洁,同时也比较符合大家的开发思路。
条件处理
假设请求某条数据,收到响应后,需要根据响应值来决定下一步如何操作,就不得不在 then 中写条件判断,代码如下:
1mounted() {2 this.fetchId()3 .then((id) => {4 // 这里是条件5 if (id < 2) {6 this.fetchDetail()7 .then(products => {8 this.products = products;9 console.log('get products', products);10 })11 } else {12 console.info('fetch other request');13 }14 });15},
当然,实际情况可能比这个更为复杂。当你想添加或修改某个功能,估计想离职的心思都有了。
让我们看看使用 Async/Await ,如何处理这种情况。
1async mounted() {2 const id = await this.fetchId();3 if (id < 2) {4 const products = await this.fetchDetail();5 console.log('get products', products);6 } else {7 console.info('fetch other request');8 }9},
很明显,比上一个简洁清晰多了。
中间值处理
还以上个例子来说,只是这次某个接口,需要依赖前两个接口的数据,代码如下:
1mounted() {2 // 请求 A 接口3 this.fetchA(10)4 .then(aId => {5 // 请求 B 接口6 this.fetchB(aId)7 .then(bId => {8 // 请求 C 接口9 // 但 C 接口 依赖 aId 和 bId10 this.fetchC(aId, bId)11 .then(() => {12 console.info('do something');13 });14 })15 });16},
和上一个情况大致类似,单看这些缩紧就够头痛了,更何况实际情况要比这个还要复杂。
1Async/Await 如何处理这种情况呢?2async mounted() {3 const aId = await this.fetchA(10);4 const bId = await this.fetchB(aId);5 const cId = await this.fetchC(aId, bId);6},
await 任意代码
你可以用 await 同步语句,也可以是异步语句。举个例子
1async mounted() {2 // 等同于 Promise.resolve(5)3 const id = await 5;4},
这看起来没有什么用,但是在我们写类库以及函数时,可以统一返回 Promise 对象,避免使用者再次转换为 Promise 对象。
总结
通过对比两者的区别,很容易看到 Async/Await 比 Promise 友好。
虽然 Async/Await 使异步代码不再明显,但它的直观性,大大提高代码的可读性和可维护性。
而我们不就是一直都在追求这个目标。
希望大家在自己代码中多多使用。不止为了自己,更重要的为了其他同学,使他们能够很轻松,友好的修改自己的代码。