在本教程中,您将学习如何使用 JavaScript async
/ await
关键词编写异步代码。
请注意,要了解 async / await 工作原理,您需要了解 promise 的工作原理。
JavaScript async / await 关键字介绍
以前,处理异步操作,通常是使用回调函数。但是,当你嵌套很多回调函数时,代码就会更难维护。你最终会遇到一个臭名昭著的问题,称为回调黑洞。
假设您需要按以下顺序执行三个异步操作:
- 从数据库中获取一个用户。
- 从 API 获取用户的服务。
- 根据服务器提供的服务计算服务费用。
以下函数说明了这三个任务。请注意,我们使用 setTimeout()
函数来模拟异步操作。
function getUser(userId, callback) {
console.log('Get user from the database.');
setTimeout(() => {
callback({
userId: userId,
username: 'john'
});
}, 1000);
}
function getServices(user, callback) {
console.log(`Get services of ${user.username} from the API.`);
setTimeout(() => {
callback(['Email', 'VPN', 'CDN']);
}, 2 * 1000);
}
function getServiceCost(services, callback) {
console.log(`Calculate service costs of ${services}.`);
setTimeout(() => {
callback(services.length * 100);
}, 3 * 1000);
}
下面展示嵌套的回调函数:
getUser(100, (user) => {
getServices(user, (services) => {
getServiceCost(services, (cost) => {
console.log(`The service cost is ${cost}`);
});
});
});
输出:
Get user from the database.
Get services of john from the API.
Calculate service costs of Email,VPN,CDN.
The service cost is 300
为了避免这种回调黑洞问题,ES6 引入允许您以更易于管理的方式编写异步代码的 Promise。
首先,您需要在每个函数返回一个 Promise
:
function getUser(userId) {
return new Promise((resolve, reject) => {
console.log('Get user from the database.');
setTimeout(() => {
resolve({
userId: userId,
username: 'john'
});
}, 1000);
})
}
function getServices(user) {
return new Promise((resolve, reject) => {
console.log(`Get services of ${user.username} from the API.`);
setTimeout(() => {
resolve(['Email', 'VPN', 'CDN']);
}, 2 * 1000);
});
}
function getServiceCost(services) {
return new Promise((resolve, reject) => {
console.log(`Calculate service costs of ${services}.`);
setTimeout(() => {
resolve(services.length * 100);
}, 3 * 1000);
});
}
然后,您将 Promise 进行链式调用:
getUser(100)
.then(getServices)
.then(getServiceCost)
.then(console.log);
ES2017 引入了构建在 promises 之上的 async
/ await
关键字,允许您编写看起来更像同步代码并且更具可读性的异步代码。从技术上讲,async
/ await
是 promise 的语法糖。
如果一个函数 f
返回一个 Promise,你可以把 await
关键字放在函数调用的前面,像这样:
let result = await f();
await
将等待从 f()
函数返回的 Promise
。关键字 await
只能在函数内部使用。
下面定义一个 async
函数,依次调用这三个异步操作:
async function showServiceCost() {
let user = await getUser(100);
let services = await getServices(user);
let cost = await getServiceCost(services);
console.log(`The service cost is ${cost}`);
}
showServiceCost();
如您所见,异步代码现在看起来像同步代码。让我们深入了解 async / await 关键字。
async 关键词
async
关键字允许您定义处理异步操作的函数。要定义 async
函数,请 async
关键字放在 function 关键字前面,如下所示:
async function sayHi() {
return 'Hi';
}
异步函数通过事件轮询异步执行。它总是返回一个 Promise
.
在此示例中,因为 sayHi()
函数返回一个 Promise
,您可以像下面一样的 方式使用它,如下所示:
sayHi().then(console.log);
您还可以从 sayHi()
函数显式返回一个 Promise
,如以下代码所示:
async function sayHi() {
return Promise.resolve('Hi');
}
效果是一样的。
除了普通函数外,您还可以在函数表达式使用 async
关键字:
let sayHi = async function () {
return 'Hi';
}
箭头函数:
let sayHi = async () => 'Hi';
类的方法:
class Greeter {
async sayHi() {
return 'Hi';
}
}
await 关键词
您使用 await
关键字等待 Promise
以解决或拒绝状态解决。您只能在 async
函数内部使用 await
关键字:
async function display() {
let result = await sayHi();
console.log(result);
}
在此示例中,await
关键字指示 JavaScript 引擎在显示消息之前等待 sayHi()
函数完成。
请注意,如果您在async
函数外部使用await
,则会抛出错误。
错误处理
如果 await promise
已经得到解决,则返回结果。然而,当 promise 被拒绝时,将 await promise
抛出一个错误,就像有一个 throw
声明一样。
以下代码:
async function getUser(userId) {
await Promise.reject(new Error('Invalid User Id'));
}
与此相同:
async function getUser(userId) {
throw new Error('Invalid User Id');
}
在实际场景中,promise 抛出错误需要一段时间。
您可以使用 try...catch
语句来捕获 Promise 的错误,也可以是 throw
的错误:
async function getUser(userId) {
try {
const user = await Promise.reject(new Error('Invalid User Id'));
} catch(error) {
console.log(error);
}
}
可以捕获由一个或多个 await promise
引起的错误:
async function showServiceCost() {
try {
let user = await getUser(100);
let services = await getServices(user);
let cost = await getServiceCost(services);
console.log(`The service cost is ${cost}`);
} catch(error) {
console.log(error);
}
}
结论
在本教程中,您学习如何使用 JavaScript async
/ await
关键字编写看起来像同步代码的异步代码。