Skip to content

第 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 之上的“语法糖”,它允许你像编写同步代码一样编写异步代码。

使用规则:

  1. 关键字 await 只能在标记为 async 的函数内部使用。
  2. await 放置在返回 Promise 的调用(例如 fetch()response.json())之前。
  3. 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)。

缺点:

  • 容易忘记 awaitasync,这会导致错误。

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);
    }
}


巩固测验

1. `async/await` 是...

2. 在使用 `await` 的函数内部,哪个关键字是必需的?

3. `async/await` 相对于 `.then()` 的主要优势是:

4. 如果在 `async` 函数内部忘记在 `fetch()` 前添加 `await`,会发生什么?

5. `Promise.all()` 用于:


🚀 本章总结:

你学习了两种管理异步操作的语法,并理解了为什么 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