一句话

async/await 是 Promise 的语法糖——让异步代码看起来像同步

async function getUser(id) {
    const r = await fetch(`/users/${id}`);
    return r.json();
}

等价于:

function getUser(id) {
    return fetch(`/users/${id}`).then(r => r.json());
}

两个关键字

含义
async 标记函数返回 Promise
await "等"一个 Promise 解析(只在 async 函数里能用

await 后面通常是 Promise,但实际上任何值都行:

const x = await 42;             // x = 42(自动包成 Promise.resolve(42))
const y = await fetch('/api');  // y = Response

错误处理:try/catch

async function getUser(id) {
    try {
        const r = await fetch(`/users/${id}`);
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        return await r.json();
    } catch (err) {
        console.error('Failed:', err);
        throw err;       // 让调用方处理
    }
}

跟同步代码一样自然——这是 async/await 比 .then 强的地方。

并行 vs 顺序(最易踩的坑

顺序(一个等一个)

async function loadAll() {
    const user = await fetch('/user');
    const posts = await fetch('/posts');         // 等 user 完成才开始
    const comments = await fetch('/comments');   // 等 posts 完成才开始
    return { user, posts, comments };
}
// 总耗时 = user + posts + comments

并行(同时发起)

async function loadAll() {
    const [user, posts, comments] = await Promise.all([
        fetch('/user'),
        fetch('/posts'),
        fetch('/comments'),
    ]);
    return { user, posts, comments };
}
// 总耗时 = max(user, posts, comments)

如果几个请求互不依赖,永远用 Promise.all 并行——不要顺序。

顶层 await(ESM 独有)

// 在 ESM 模块顶层直接 await
const config = await fetch('/config').then(r => r.json());
console.log(config);

CJS 文件做不到——要包在 async 函数里。

循环里的异步

顺序:for-of

for (const id of ids) {
    const r = await fetch(`/users/${id}`);
    // ... 处理 r
}

并行:Promise.all + map

const results = await Promise.all(
    ids.map(id => fetch(`/users/${id}`))
);

forEach 不支持 await

// ❌ 不会等
ids.forEach(async (id) => {
    await fetch(`/users/${id}`);    // 这些"飞了"
});
// 下面这行立刻执行,不等 fetch
console.log('done');

限流并行

并发太多 → 撑爆服务器 / 内存。限制并发:

import pLimit from 'p-limit';

const limit = pLimit(5);            // 最多 5 个同时
const results = await Promise.all(
    ids.map(id => limit(() => fetch(`/users/${id}`)))
);

或自己写简单 chunked:

async function processChunk(items, fn, size = 5) {
    const results = [];
    for (let i = 0; i < items.length; i += size) {
        const chunk = items.slice(i, i + size);
        const chunkResults = await Promise.all(chunk.map(fn));
        results.push(...chunkResults);
    }
    return results;
}

超时

async function fetchWithTimeout(url, ms = 5000) {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), ms);
    try {
        return await fetch(url, { signal: controller.signal });
    } finally {
        clearTimeout(timer);
    }
}

AbortController 是现代取消异步的标准方式。

几个反直觉的点

return await vs return

async function foo() {
    return await somePromise;       // 等价于 return somePromise
}

两者结果一样。但 try-catch 里要用 return await——不然错误会跑到调用方而不是当前 catch。

async 函数总是返回 Promise

async function answer() { return 42; }
answer();                            // Promise<42>
const val = await answer();          // 42

即使你只是返回一个普通值——async 自动包成 Promise。

await 不能在普通函数里用

function foo() {
    const x = await bar();           // ❌ SyntaxError
}

async function foo() {
    const x = await bar();           // ✓
}

调试

异步代码的栈跟踪历史上很糟——Node 12+ 有 async stack traces

node --async-stack-traces app.js

新版默认开了。能看到 await 调用的完整路径。

  • 顺序 vs 并行:互不依赖的多个 await → 用 Promise.all
  • forEach 不能 await:用 for-of 或 Promise.all(map)
  • 忘了 await:返回的是 Promise 不是值,下一行可能行为怪
  • 错误没 catch:unhandled rejection;至少加 process.on('unhandledRejection') 兜底

下一篇:event loop 深入。