• 导航

不得不使用 Aysnc/Await 的原因

前端杂烩 2024-05-09 31 次浏览
目前 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 的方式:
      
  // 省略部分Vue代码
methods: {
fetchDetail() {
// 调用接口数据
return axios.get('https://randomapi.com/api/b07f414474d47329b82840fba25dfabf')
.then(response => response.data)
.then(response => response.results);
},
},
mounted() {
console.log('start mounted');
this.fetchDetail()
.then(products => {
this.products = products;
console.log('done', products);
});
console.log('end mounted');
},
// 结果会打印
// start mounted
// end mounted
// done [...]
现在,我们改成 Async / Await 语法
      
  // 省略部分Vue代码
async mounted() {
console.log('start mounted');
const products = await this.fetchDetail();
this.products = products;
console.log('done', products);
console.log('end mounted');
},
// 结果会打印
// start mounted
// done [...]
// end mounted
可以看出以上两种方式的区别:
1.
在 「mounted」方法之前有个 「async」关键词,且 「await」关键词必须在有 「async」函数的内部出现。含有「async」的函数,会隐式返回一个 promise 对象。
2.
我们不能在顶层代码中直接使用 「await」调用,因为顶层代码没有「async」函数。这点在 Node.js 中会遇到。
      
  // app.js
// 这里不会执行,因为 app.js 没有 async 关键词
// const result = await fetchDetail();

// 如果需要执行只能用 then
fetchDetail().then(() => {
// TODO
});
3.
「await」函数是“同步”的,从打印结果中明显可以看出。

为什么 Async/Await 更好?

1.
代码清晰简洁明了
大家想象下,如果有多个请求,且下一个请求都是从上一个请求中获取数据,如果使用 Promise ,就会一直 then 嵌套 then ,陷入到嵌套地狱中。单从代码的可读性和可维护性方面就会大打折扣。而使用 Async ,则不会出现嵌套的情况,大大提高了代码的可读性和可维护性。
2.
错误捕捉
针对 Promise 捕捉错误,需要谨慎处理。因为 Promise 是异步的,try catch 不能写在最外层简单。 拿上面的 fetchDetail 举例。
      
  try {
this.fetchDetail()
.then(products => {
this.products = products;
// 转换错误
const json = JSON.parse(undefined);
this.json = json;
console.log('done', products);
})
// 需要通过这种方式捕捉错误
// .catch(() => {
// console.info('error');
// });;
} catch (error) {
console.error(error);
}
在最外层直接 try catch ,是捕捉不到异常或错误,需要调用 Promise 中的 catch 才可以。
我们看看使用 async / await 如何捕捉错误。
      
  async mounted() {
console.log('start mounted');
try {
const products = JSON.parse(await this.fetchDetail());
this.products = products;
console.log('done', products);
} catch (err) {
// 会执行到这里
console.error(err);
}
console.log('end mounted');
},
可以看到,这种捕捉方式,比较清晰简洁,同时也比较符合大家的开发思路。
3.
条件处理
假设请求某条数据,收到响应后,需要根据响应值来决定下一步如何操作,就不得不在 then 中写条件判断,代码如下:
      
  mounted() {
this.fetchId()
.then((id) => {
// 这里是条件
if (id < 2) {
this.fetchDetail()
.then(products => {
this.products = products;
console.log('get products', products);
})
} else {
console.info('fetch other request');
}
});
},
当然,实际情况可能比这个更为复杂。当你想添加或修改某个功能,估计想离职的心思都有了。
让我们看看使用 Async/Await ,如何处理这种情况。
      
  async mounted() {
const id = await this.fetchId();
if (id < 2) {
const products = await this.fetchDetail();
console.log('get products', products);
} else {
console.info('fetch other request');
}
},
很明显,比上一个简洁清晰多了。
4.
中间值处理
还以上个例子来说,只是这次某个接口,需要依赖前两个接口的数据,代码如下:
      
  mounted() {
// 请求 A 接口
this.fetchA(10)
.then(aId => {
// 请求 B 接口
this.fetchB(aId)
.then(bId => {
// 请求 C 接口
// 但 C 接口 依赖 aId 和 bId
this.fetchC(aId, bId)
.then(() => {
console.info('do something');
});
})
});
},
和上一个情况大致类似,单看这些缩紧就够头痛了,更何况实际情况要比这个还要复杂。
Async/Await 如何处理这种情况呢?
      
  async mounted() {
const aId = await this.fetchA(10);
const bId = await this.fetchB(aId);
const cId = await this.fetchC(aId, bId);
},
5.
await 任意代码
你可以用 await 同步语句,也可以是异步语句。举个例子
      
  async mounted() {
// 等同于 Promise.resolve(5)
const id = await 5;
},
这看起来没有什么用,但是在我们写类库以及函数时,可以统一返回 Promise 对象,避免使用者再次转换为 Promise 对象。

总结

通过对比两者的区别,很容易看到 Async/Await 比 Promise 友好。
虽然 Async/Await 使异步代码不再明显,但它的直观性,大大提高代码的可读性和可维护性。
而我们不就是一直都在追求这个目标。
希望大家在自己代码中多多使用。不止为了自己,更重要的为了其他同学,使他们能够很轻松,友好的修改自己的代码。