一句话
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 深入。