Async/Await 用法详解

asyncawait 是现代 JavaScript(ES2017+)引入的语法,用于简化异步编程,基于 Promise 提供更直观的代码结构。它让异步代码看起来像同步代码,提高可读性和维护性。本指南将详细讲解 async/await 的用法、场景、注意事项,并提供清晰示例。以下内容假设您熟悉 JavaScript 和 Promise。


1. 基本概念

  • async 关键字

    • 用于声明异步函数。
    • async 函数总是返回一个 Promise 对象(即使返回值不是 Promise,会自动包装为 resolved 的 Promise)。
    • 语法:async function functionName() { ... }
  • await 关键字

    • 只能在 async 函数内使用。
    • 用于暂停执行,等待 Promise 解析(resolve)或拒绝(reject)。
    • await 后接 Promise 对象,返回其 resolved 值。

核心机制async/await 是 Promise 的语法糖,简化 .then().catch() 的链式调用。


2. 基本用法

2.1 简单示例

以下是一个使用 async/await 模拟异步请求的例子:

// 模拟异步函数(返回 Promise)
function fetchData() {
    return new Promise((resolve) => {
        setTimeout(() => resolve("Data fetched!"), 1000);
    });
}

// 异步函数
async function getData() {
    console.log("Starting...");
    const result = await fetchData(); // 等待 Promise 解析
    console.log(result); // 输出 "Data fetched!"
    console.log("Finished!");
}

// 调用
getData();

输出(1秒后):

Starting...
Data fetched!
Finished!

解释

  • async function getData() 声明异步函数。
  • await fetchData() 暂停执行,直到 fetchData 的 Promise 解析。
  • result 接收 Promise 的 resolved 值(“Data fetched!”)。
2.2 与 Promise 对比

使用 .then() 的写法:

function getDataWithPromise() {
    console.log("Starting...");
    fetchData()
        .then(result => {
            console.log(result);
            console.log("Finished!");
        });
}

async/await 使代码更像同步逻辑,减少嵌套,提高可读性。


3. 错误处理

await 的 Promise 可能被拒绝(reject),需要用 try...catch 捕获错误。

function fetchDataWithError() {
    return new Promise((resolve, reject) => {
        setTimeout(() => reject(new Error("Fetch failed!")), 1000);
    });
}

async function getData() {
    try {
        console.log("Starting...");
        const result = await fetchDataWithError();
        console.log(result); // 不会执行
    } catch (error) {
        console.error("Error:", error.message); // 输出 "Error: Fetch failed!"
    }
    console.log("Finished!");
}

getData();

输出(1秒后):

Starting...
Error: Fetch failed!
Finished!

说明

  • try 块包含 await 语句,捕获可能的错误。
  • catch 块处理 Promise 的 reject 状态。
  • 等价于 .catch(error => console.error(error))

4. 高级用法

4.1 串行执行(Sequential)

多个异步操作按顺序执行:

async function fetchMultiple() {
    try {
        const data1 = await fetchData("Data 1");
        console.log(data1);
        const data2 = await fetchData("Data 2");
        console.log(data2);
    } catch (error) {
        console.error("Error:", error.message);
    }
}

function fetchData(value) {
    return new Promise(resolve => setTimeout(() => resolve(value), 1000));
}

fetchMultiple();

输出(每秒输出一行):

Data 1
Data 2

说明await 使异步调用按顺序执行,第二个 await 等待第一个完成。

4.2 并行执行(Parallel)

使用 Promise.all 同时执行多个异步操作:

async function fetchParallel() {
    try {
        const [data1, data2] = await Promise.all([
            fetchData("Data 1"),
            fetchData("Data 2")
        ]);
        console.log(data1, data2);
    } catch (error) {
        console.error("Error:", error.message);
    }
}

fetchParallel();

输出(1秒后同时输出):

Data 1 Data 2

说明

  • Promise.all 接受一个 Promise 数组,返回所有 Promise 的 resolved 值数组。
  • 比串行更快,因为两个 fetchData 同时执行。
4.3 结合循环

在循环中使用 await

async function processItems(items) {
    for (const item of items) {
        const result = await fetchData(item);
        console.log(result);
    }
}

processItems(["Item 1", "Item 2"]);

输出(每秒输出一行):

Item 1
Item 2

并行循环(更快):

async function processItemsParallel(items) {
    const promises = items.map(item => fetchData(item));
    const results = await Promise.all(promises);
    results.forEach(result => console.log(result));
}

5. 实际应用场景

5.1 HTTP 请求

使用 fetch API 获取数据:

async function fetchUser(userId) {
    try {
        const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
        if (!response.ok) throw new Error("Network error");
        const user = await response.json();
        console.log("User:", user.name);
    } catch (error) {
        console.error("Error:", error.message);
    }
}

fetchUser(1);

输出User: Leanne Graham(假设 API 返回该数据)。

5.2 超时处理

结合 setTimeout 实现超时:

function timeout(ms) {
    return new Promise((_, reject) => {
        setTimeout(() => reject(new Error("Timeout")), ms);
    });
}

async function fetchWithTimeout() {
    try {
        const result = await Promise.race([
            fetchData("Data"),
            timeout(500) // 500ms 超时
        ]);
        console.log(result);
    } catch (error) {
        console.error("Error:", error.message);
    }
}

fetchWithTimeout();

输出(如果 fetchData 超过 500ms):Error: Timeout


6. 好处

  1. 可读性强

    • 代码接近同步写法,避免 .then 嵌套(回调地狱)。
    • 逻辑清晰,易于维护。
  2. 错误处理简洁

    • try...catch 统一处理同步和异步错误。
    • .catch 链更直观。
  3. 灵活性

    • 支持串行、并行、循环等多种异步模式。
    • 兼容所有 Promise-based API(如 fetch、Node.js 异步函数)。

7. 注意事项

  1. 只能在 async 函数内使用 await

    • 在普通函数中使用 await 会抛出语法错误。
    • 顶层 await(Top-level await)仅在 ES Modules 或特定环境(如 Node.js REPL)支持。
  2. 性能考虑

    • 串行 await 可能导致等待时间累加,必要时用 Promise.all
    • 避免在循环中逐个 await,考虑并行处理。
  3. 错误处理

    • 未捕获的错误会导致 Promise 拒绝,需用 try...catch
    • Promise.all 中任一 Promise 拒绝,整个操作失败(可用 Promise.allSettled 替代)。
  4. 兼容性

    • async/await 在现代浏览器和 Node.js(v7.6+)广泛支持。
    • 老旧环境需 Babel 转译。

8. 常见问题与故障排除

问题 原因 解决方案
SyntaxError: await is only valid in async function 在非 async 函数中使用 await 确保函数声明为 async
异步操作顺序错误 过多串行 await 使用 Promise.all 并行执行。
未捕获错误导致程序崩溃 缺少 try...catch 始终用 try...catch 包裹 await
await 后返回 Promise 误认为 await 返回非 Promise 值 await 总是解析 Promise 的 resolved 值。

9. 高级示例:链式异步操作

模拟用户数据和帖子加载:

async function fetchUserAndPosts(userId) {
    try {
        // 获取用户
        const userResponse = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
        if (!userResponse.ok) throw new Error("Failed to fetch user");
        const user = await userResponse.json();
        console.log("User:", user.name);

        // 获取用户帖子
        const postsResponse = await fetch(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`);
        if (!postsResponse.ok) throw new Error("Failed to fetch posts");
        const posts = await postsResponse.json();
        console.log("Posts count:", posts.length);

        return { user, posts };
    } catch (error) {
        console.error("Error:", error.message);
        throw error; // 可选择抛出错误供调用者处理
    }
}

async function main() {
    const result = await fetchUserAndPosts(1);
    console.log("Result:", result);
}

main();

输出

User: Leanne Graham
Posts count: 10
Result: { user: {...}, posts: [...] }

说明展示了 async/await 在真实 API 调用中的链式操作和错误处理。


10. 总结

  • 定义async 声明异步函数,返回 Promise;await 暂停执行,解析 Promise。
  • 用法:简化异步代码,支持串行、并行、循环、错误处理。
  • 好处:可读性强、错误处理简洁、灵活性高。
  • 场景:HTTP 请求、文件操作、数据库查询等。
  • 注意:需在 async 函数内使用,合理管理串行/并行,始终处理错误。

如果您需要特定场景的 async/await 示例(如 Node.js 文件操作、数据库查询)或想深入某方面(如性能优化),请告诉我,我可以进一步定制代码!

Logo

分享最新、最前沿的AI大模型技术,吸纳国内前几批AI大模型开发者

更多推荐