第 4.5 章:Async/await 对比 Promise
学习时间: 30 分钟
1. 异步:两种“空间通信”管理方式
想象一下,任务控制中心(MCC)向火星发送了一条指令。回复将在几分钟后才能收到。在这段时间内如何安排工作?
方式 1:“回调协议”(带 .then()
的 Promise)
你发送一条指令并给出指示:“当收到回复时,那么执行这个函数”。这就像一个事件链。
方式 2:“等待模式”(Async/await) 你说:“我将等待这条指令的回复,但不会阻塞其他控制台。” 你就像是将这个特定任务的执行暂停,让任务控制中心的其他部分继续工作。
两种方式都解决了同一个问题——管理异步操作。async/await
只是一种更现代、更易读的语法,它在 Promise 的基础上工作。
💡 太空类比:
- 带
.then()
的 Promise:这就像在便利贴上写道:“当火星车发送照片时,将其传输到分析部门。”- Async/await:这就像对助手说:“等等火星车的照片,我先去计算新火箭的发射数据。”
2. 带 .then()
的 Promise:经典命令链
这是 JavaScript 中处理异步的基本方式,我们在第 4.1 章中曾使用过。
回顾我们的第一个代码:
function getIssPositionWithPromises() {
console.log('正在通过“Promise”协议发送请求...');
fetch('http://api.open-notify.org/iss-now.json')
.then(response => {
// 阶段 1:收到响应
if (!response.ok) {
throw new Error(`HTTP 错误:${response.status}`);
}
return response.json(); // 返回一个新的 Promise
})
.then(data => {
// 阶段 2:数据已解析
console.log('通过“Promise”协议获取的数据:', data.iss_position);
})
.catch(error => {
// 阶段 3(错误):在任何阶段都出错了
console.error('“Promise”协议通信失败:', error);
});
console.log('...命令已发送,任务控制中心继续工作...');
}
优点:
- 清晰的动作链。
- 非常适合简单的顺序操作。
缺点:
- “回调地狱”(Callback Hell): 当有大量嵌套异步操作时,代码可能变成一个由
.then()
组成的“阶梯”,难以阅读。 - 错误处理可能不够直观。
3. Async/await:现代同步风格
async/await
是 Promise 之上的“语法糖”,它允许你像编写同步代码一样编写异步代码。
使用规则:
- 关键字
await
只能在标记为async
的函数内部使用。 await
放置在返回 Promise 的调用(例如fetch()
或response.json()
)之前。await
会“暂停”async
函数的执行,直到 Promise 被解决(resolved),并返回其结果。
相同的代码,用 async/await
重写:
async function getIssPositionWithAsyncAwait() {
console.log('正在通过“Async/await”协议发送请求...');
try {
// 阶段 1:等待服务器响应
const response = await fetch('http://api.open-notify.org/iss-now.json');
if (!response.ok) {
throw new Error(`HTTP 错误:${response.status}`);
}
// 阶段 2:等待响应体转换为 JSON
const data = await response.json();
console.log('通过“Async/await”协议获取的数据:', data.iss_position);
} catch (error) {
// 阶段 3(错误):捕获 try 块中的任何错误
console.error('“Async/await”协议通信失败:', error);
}
console.log('...命令已发送,任务控制中心继续工作...');
}
优点:
- 可读性: 代码看起来几乎和普通的同步代码一样,很容易从上到下阅读。
- 错误处理: 使用标准且熟悉的
try...catch
块。 - 调试: 更容易调试,因为可以在每个带有
await
的行上设置断点(breakpoints)。
缺点:
- 容易忘记
await
或async
,这会导致错误。
4. 何时使用哪种协议?
情况 | 推荐方法 | 原因? |
---|---|---|
大多数情况 | async/await |
代码更清晰,更易读和调试。这是现代标准。 |
简单 1-2 步链 | 带 .then() 的 Promise |
完全适用,代码保持紧凑。 |
并行执行多个请求 | Promise.all() |
此方法允许同时启动多个 Promise 并等待它们全部完成。async/await 与它完美结合。 |
Promise.all()
示例:
async function getParallelData() {
try {
// 同时启动两个请求
const [shipsResponse, launchesResponse] = await Promise.all([
fetch('https://api.spacexdata.com/v4/rockets'),
fetch('https://api.spacexdata.com/v4/launches/latest')
]);
if (!shipsResponse.ok || !launchesResponse.ok) {
throw new Error('其中一个请求失败了!');
}
const rockets = await shipsResponse.json();
const latestLaunch = await launchesResponse.json();
console.log(`舰队中的火箭总数:${rockets.length}`);
console.log(`最新发射:${latestLaunch.name}`);
} catch (error) {
console.error('获取并行数据时出错:', error);
}
}
巩固测验
🚀 本章总结:
你学习了两种管理异步操作的语法,并理解了为什么 async/await
在大多数现代项目中是首选。
- 🔗 你回顾了关于带
.then()
的 Promise 的知识。 - 🛠️ 你深入理解了
async/await
的工作原理及其优势。 - ⚡ 你了解了
Promise.all
用于执行并行请求。
通信协议已掌握! 在本节的最后一章中,我们将把所有知识融会贯通,并完善我们的“飞行控制中心”,为所有 CRUD 操作创建一个完整的界面。
📌 实践:
- 将你
app.js
中所有仍然使用.then()
的函数重写为async/await
语法。- 尝试在
Promise.all()
中添加另一个请求(例如,访问https://api.spacexdata.com/v4/starlink
)并输出数据。⚠️ 如果出现错误:
await is only valid in async functions
:确保你使用await
的函数已被标记为async
。- 变量包含
[object Promise]
:你忘记在返回 Promise 的函数前加上await
。