一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第22天,点击查看活动详情
在js中,我们有三种编写异步代码的方式。
第一种方法是使用回调。 当异步操作完成时,会执行一个回调函数(意思是操作完成后给我回电话):
const callbackFunction = result = {
// Called when the operation completes
};
asyncOperation(params, callbackFunction);
但是一旦处理多个异步操作,回调函数就会相互嵌套,从而导致回调地狱。
承诺是一个异步操作的结果占位符。 通过使用 Promise,可以更轻松地处理异步操作。
const promise = asyncOperation(params);
promise.then(result => {
// Called when the operation completes
});
你见过.then().then()...then() 承诺链🚂🚃🚃🚃🚃吗? Promise的一个问题是它们有的时候比较冗长。
最后,第三次突破是 async/await 语法(从 ES2017 开始)。 它让我们以简洁和同步的方式编写异步代码:
(async function() {
const result = await asyncOperation(params);
// Called when the operation completes
})();
在这篇文章中,将逐步解释如何在 JavaScript 中使用 async/await。
注意:async/await 是 Promise 之上的语法糖。
1.同步添加
因为标题提到了一个有趣的解释,下面将逐步解释关于贪婪老板的async/await故事。
让我们从一个简单的(同步)函数开始,它的任务是计算加薪:
function increaseSalary(base, increase) {
const newSalary = base + increase;
console.log(`New salary: ${newSalary}`);
return newSalary;
}
increaseSalary(1000, 200); // => 1200
// logs "New salary: 1200"
increaseSalary() 是一个对 2 个数字求和的函数。 n1 + n2 是同步操作。
老板不希望员工的工资快速增加☹。 所以你不能在increaseSalary() 函数中使用加法运算符+。
相反,您必须使用一个需要消耗2秒时间的慢速函数来汇总数字。 让我们将函数命名为 slowAddition():
function slowAddition(n1, n2) {
return new Promise(resolve => {
setTimeout(() => resolve(n1 + n2), 2000);
});
}
slowAddition(1, 5).then(sum => console.log(sum));
// 2秒后打印 "6"
slowAddition() 返回一个 promise,它在 2 秒的后resolve为参数的和。
如何更改increaseSalary()函数来支持慢加?
2.异步添加
第一个初步的尝试是用slow Addition(n1, n2)的调用替换 n1 + n2 表达式:
function increaseSalary(base, increase) {
const newSalary = slowAddition(base, increase);
console.log(`New salary: ${newSalary}`);
return newSalary;
}
increaseSalary(1000, 100); // => [object Promise]
// Logs "New salary [object Promise]"
不幸的是,函数increaseSalary() 不知道如何处理promise。 该函数把promise当成常规对象:它不知道如何以及何时从promise 中提取值。
现在是时候使用async/await语法,让increaseSalary() 知道如何处理slowAddition()返回的promise。
首先,您需要在函数声明前面添加 async 关键字。 然后,在函数体内部,需要使用await 操作符让函数等待promise 被resolve。
让我们对 increaseSalary() 函数进行这些更改:
async function increaseSalary(base, increase) {
const newSalary = await slowAddition(base, increase);
console.log(`New salary: ${newSalary}`);
return newSalary;
}
increaseSalary(1000, 200); // => [object Promise]
// 2秒后打印 "New salary 1200"
JavaScript 以如下方式计算处理 const newSalary = await slowAddition(base, increase):
- await slowAddition(base, increase) 暂停 increaseSalary() 函数执行
- 2秒后slowAddition(base, increase)返回的promise resolved了
- increaseSalary() 函数执行恢复
- newSalary 被分配了promise的resolve值1200 (1000+200)
- 函数执行照常继续。
简单来说,当 JavaScript 在 async 函数中遇到 await promise 时,它会暂停函数执行,直到 promise 被resolve。 承诺的resolve值成为await promise的结果。
即使 return newSalary 返回数字 1200,如果您查看函数 increaseSalary(1000, 200) 返回的实际值——它仍然是一个承诺!
异步函数总是返回一个promise,它的resolve值为函数内返回值:
increaseSalary(1000, 200).then(salary => {
salary; // => 1200
});
返回 promise 的异步函数是一件好事,因为这样我们就可以嵌套异步函数。
3.打破异步加法
老板要求慢慢加薪是不公平的。你决定破坏slowAddition() 函数。
你修改慢加函数以拒绝数字加法:
function slowAdditionBroken(n1, n2) {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('Unable to sum numbers')), 3000);
});
}
slowAdditionBroken(1, 5).catch(e => console.log(e.message));
// 3秒后打印 "Unable to sum numbers"
如何在异步函数calculateSalary()中处理被rejected的promise? 只需将 await 运算符写在 try/catch 子句中:
async function increaseSalaryBroken(base, increase) {
let newSalary;
try {
newSalary = await slowAdditionBroken(base, increase);
} catch (e) {
console.log(`Error: ${e.message}`);
newSalary = base * 2;
}
console.log(`New salary: ${newSalary}`);
return newSalary;
}
increaseSalaryBroken(1000, 200);
// 3秒后打印
// "Error: Unable to sum numbers", "New salary: 2000"
在表达式 await slowAdditionBroken(base, increase) 处,JavaScript 暂停函数执行并等待promise fulfilled(承诺成功 resolved)或被rejected(发生错误)。
3 秒后,promise 被rejected并返回 new Error('Unable to sum numbers')。 由于rejected,函数的执行会跳转到基本工资乘以2的catch (e){ } 子句中。
吝啬鬼付了两次钱。 现在老板要付双倍工资。
如果您没有捕获被rejected的promise,则错误会传播,并且异步函数返回的承诺也是rejected状态:
async function increaseSalaryBroken(base, increase) {
const newSalary = await slowAdditionBroken(base, increase);
return newSalary;
}
increaseSalaryBroken(1000, 200).catch(e => {
e.message; // => "Unable to sum numbers"
});
4.嵌套异步函数
尽管在异步函数中 return value 表达式返回有效值而不是承诺,但当异步函数被调用时,它仍然返回一个承诺。
这是一件好事,因为 我们可以借此来嵌套异步函数!
例如,让我们编写一个异步函数,使用 slowAddition() 函数增加工资数组:
async function increaseSalaries(baseSalaries, increase) {
let newSalaries = [];
for (let baseSalary of baseSalaries) {
newSalaries.push(
await increaseSalary(baseSalary, increase);
);
}
console.log(`New salaries: ${newSalaries}`);
return newSalaries;
}
increaseSalaries([950, 800, 1000], 100);
// 6秒后打印 "New salaries: 1050,900,1100"
await increaseSalary(baseSalary, increase) 被调用 3 次。 每次 JavaScript 等待 2 秒,直到计算总和。
通过这种方式,您可以将异步函数嵌套到异步函数中。
5.并行异步
在前面对工资数组求和的示例中,求和按顺序进行:函数对每个工资暂停 2 秒。
但是你可以同时加薪! 让我们使用 Promise.all() 实用函数来同时开始所有的加薪:
async function increaseSalaries(baseSalaries, increase) {
let salariesPromises = [];
for (let baseSalary of baseSalaries) {
salariesPromises.push(
increaseSalary(baseSalary, increase)
);
}
const newSalaries = await Promise.all(salariesPromises);
console.log(`New salaries: ${newSalaries}`);
return newSalaries;
}
increaseSalaries([950, 800, 1000], 100);
// 2秒后打印 "New salaries: 1050,900,1100"
加薪任务立即开始(在increaseSalary(baseSalary, increase)附近不使用await),并在salaryPromises 中收集promise。
await Promise.all(salariesPromises) 暂停函数执行,直到所有并行处理的异步操作完成。 最后,仅在 2 秒后,newSalaries 变量包含所有增加的工资。
您已经设法在短短 2 秒内增加了所有员工的工资,即使每个操作都很慢并且需要 2 秒。 你又骗老大了!
6.实际异步示例
使用 async/await语法常见情况是fetch远程数据。 fetch() 方法非常适合与 async/await 一起使用,因为它返回一个可resolve为远程 API 返回值的promise。
例如,以下是从远程服务器获取电影列表的方法:
async function fetchMovies() {
const response = await fetch('https://api.example.com/movies');
if (!response.ok) {
throw new Error('Failed to fetch movies');
}
const movies = await response.json();
return movies;
}
await fetch('api.example.com/movies') 将暂停 fetchMovies() 执行,直到请求完成。 然后你可以使用await response.json()获取结果。
7.总结
async/await 是 Promise 之上的语法糖,它提供了一种以同步方式处理异步任务的方法。 async/await 有 4 个简单的规则:
- 处理异步任务的函数必须使用 async 关键字进行标记。
- await promise 运算符会暂停函数执行,直到 promise 成功resolved 或 rejected.。
- 如果promise 成功resolve,await 操作符返回解析值:const resolutionValue = await promise。 否则,您可以在 try/catch 中捕获被rejected的promise。
- 异步函数总是返回一个promise,这使异步函数有了嵌套的能力。