异步编程是 Web 前端工程师最难处理的问题之一,自从 Ajax 的出现后,异步编程越来越普遍,现在前端 MV* 框架的兴起让前端完全和后端分离开来,使得异步编程已经司空见惯,异步编程也是从回调函数、到事件的监听、到 Promise、到 Generator 函数,它们都在解决同样一个问题,让异步编程的调用更佳的“扁平”化,让前端工程师在异步编程中更佳容易和优美的解决问题,终于 Async/Await 出现了。
Async/Await 是什么
Async/Await 是编写异步程序的新方式,它实际上是基于 Promise 来实现的,它会让异步的代码写起来和同步代码一样。Node 已经在 7.6 版本开始支持 Async/Await,并且它已经在 ES7 的新特性中出现。
如何使用
让我们看看它到底是如何使用的,它采用了什么样神奇的语法糖。在揭开它的面纱前,我们先看看一个使用 Promise 的异步编程:我们用 axios
模块异步获取一条数据:
function getData() {
return axios.get('http://domain/path')
.then(resp => {
return resp;
});
}
getData();
如果使用 Async/Await:
async function getData() {
let resp = await axios.get('http://domain/path');
return resp;
}
getData();
第一种写法我们已经很熟悉,采用 Promise 的写法,将移步的调用编程了链式的写法,让复杂的回调更佳的清晰。让我们再看看第二种 Async/Await 写法:
-
函数
前增加关键字async
,在函数
体内使用了await
关键字,await
当然它也只能声明在函数
体内。该函数会隐式地返回一个Promise
对象,函数
体内的return
值,将会作为这个Promise
对象resolve
时的参数。 -
await axios.get()
会让函数
等到axios.get()
返回的Promise
对象resolve
之后触发。
使用 Async/Await 的 6 大优势
我们参考了 Mostafa Gaafar 高级全栈工程师的文章,来解释一下 Async/Await 如下 6 大优势:
让代码更佳的简约
回顾我们上面的代码实例中,很明显我们的代码简洁清晰了很多。我们不用写 then
,不需要创建一个匿名函数来处理返回值,或者也不需要把一个数据赋给一个我们不需要的值。我们甚至是避免了代码的嵌套。这些小的优势将会在下面的代码中更加明显。
更容易处理错误
Async/Await 最终让以同样的方式来处理同步和异步的错误成为了可能,让 try/catch
更好的使用。下面的实例中,使用 Promise,来处理异步编程的错误捕获,由于程序是异步的最外面一层 try/catch
,它无法捕获到 Promise 内部发生的错误,我们只能再嵌套一层 try/catch
来解决:
function getData() {
try {
axios.get('http://domain/path')
.then(resp => {
try {
let json = JSON.parse(resp);
}
catch (err) {
console.log(err);
}
return json;
});
}
catch (err) {
console.log(err);
}
}
我们看看使用了 Async/Await 的异步编程是如何处理错误的:
async function getData() {
let data;
try {
data = JSON.parse(await axios.get('http://domain/path'));
}
catch (err) {
console.log(err);
}
return data;
}
更容易处理条件判别
想象一下这样的业务逻辑:我们先要获取一些数据,然后通过获取到的数据来判断是否继续获取更多的详细数据:
function getData() {
return axios.get('http://domain/path').then(resp => {
if(resp && resp.data && resp.data.yes) {
return axios.get('http://domain/path1').then(resp1 => {
return resp1;
})
}
else {
return resp
}
});
}
这样的写法会让你很头疼,嵌套太多会导致很多变量的遗失,让逻辑难以处理。接下来让我们看看 Async/Await 编程如何让代码更佳容易阅读:
async function getData() {
let data = await axios.get('http://domain/path');
if(data.yes) {
let data1 = await axios.get('http://domain/path1');
return data1;
}
else {
return data;
}
}
更容易处中间值
你可能会发现自己会有这样的情况:你调用了 promise1
然后将它返回给 promise2
,最后又奖两个 promise 的结果都返回给 promise3
。你的代码很可能会写成这样:
function getData() {
return promise1()
.then(value1 => {
return promise2(value1)
.then(value2 => {
return promise3(value1, value2)
})
})
}
同样的逻辑我们用 Async/Await 编写:
async function getData() {
const value1 = await promise1()
const value2 = await promise2(value1)
return promise3(value1, value2)
}
错误堆栈信息
想象一下通过多个链式调用 Promise :
function getData() {
return callAPromise()
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => callAPromise())
.then(() => {
throw new Error("oops");
});
}
getData().catch(err => {
console.log(err);
// output
// Error: oops at callAPromise.then.then.then.then.then (index.js:8:13)
})
此链条的错误堆栈信息并没用线索指示错误到底出现在哪里。更糟糕的是,它会误导我们:
Debugging
最后,也是最重要的优势是,使用 Async/Await 非常容易调试,调试 Promise 有如下两个不好的地方:
- 我们不能在返回表达式的箭头函数中设置断点;
- 如果你在
.then
中设置断点,然后通过step-over
的方式进行调试,调试器不会移动到下一步,因为它只能通过同步的方式移动到下一步。
但是如果我们使用 Async/Await 你可以一步一步的正常的使用调试。
最后
Async/Await 是 JavaScript 中添加的具有革命性特征之一,它让你更容易的进行异步的编程。
网友评论