JS await 基础入门类
JS 中 async/await 不是新功能,是 Promise 的语法糖——本质还是回调。
掌握 await 只需记住 三 件事:
- await 只能用在 async 函数里,async 函数永远返回 Promise
- await 会"暂停"当前函数,但不会阻塞主线程, 其他代码照常跑
- 抛错用 try/catch,忘记 try/catch 会导致 UnhandledPromiseRejection
最佳实践:并发请求用 Promise.all,别串行 await,慢到怀疑人生。
一句话讲清 await 是什么
await 就是"等这个 Promise 出结果,再往下走"。
写回调时代我们这么写:
fetchUser(id, (user) => {
fetchOrders(user.id, (orders) => {
render(orders);
});
});
Promise 时代变成链式:
fetchUser(id)
.then((user) => fetchOrders(user.id))
.then((orders) => render(orders));
async/await 时代,写起来像同步代码:
async function load(id) {
const user = await fetchUser(id);
const orders = await fetchOrders(user.id);
render(orders);
}
代码长得像同步,但执行起来还是异步的——这是 await 最容易让人误解的地方。
async 函数的真面目
任何 async 函数都返回 Promise,不管你写没写 return。
async function foo() {
return 1;
}
foo(); // Promise { 1 }
foo().then(console.log); // 1
就算函数里 throw new Error('boom'),外面拿到的也是一个 rejected 的 Promise,不是同步抛出的异常。这意味着你不能用普通 try/catch 包住 foo() 调用本身,得包住 await foo()。
await 暂停的是函数,不是线程
这是初学者最容易踩的坑。看这段代码:
async function task() {
console.log('A');
await sleep(1000);
console.log('B');
}
task();
console.log('C');
输出顺序是 A → C → B。
await 暂停的是 task 函数的执行,主线程没停,所以 C 先打印。等 1 秒后 task 才被恢复,打印 B。
**JS 是单线程的,await 从来不会"卡住"页面。**如果你的页面卡了,那一定是同步代码在算东西,不是 await 的锅。
错误处理:try/catch 是底线
Promise 出错不抛异常,而是把 Promise 变成 rejected 状态。await 一个 rejected Promise,才会在 await 的位置真的抛错:
async function load() {
try {
const data = await fetch('/api/x').then((r) => r.json());
return data;
} catch (err) {
console.error('请求失败:', err);
return null;
}
}
没有 try/catch 又没有 .catch() 的 Promise rejection,在 Node 里会触发 UnhandledPromiseRejection,新版本直接让进程崩溃。浏览器里则会在 Console 报红。
串行还是并行?一个数量级的差距
新手最常见的反模式:
const a = await fetchA(); // 等 1s
const b = await fetchB(); // 再等 1s
const c = await fetchC(); // 再等 1s
// 总共 3s
三个请求互不依赖,却被强行串行,总耗时 3 秒。
正确写法是 Promise.all:
const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]);
// 总共 1s
判断标准很简单:后一个请求需不需要前一个的结果?
- 需要 → 串行 await
- 不需要 →
Promise.all并发
如果其中一个失败不影响其他,用 Promise.allSettled,它不会因为单个 rejected 就整体失败。
循环里的 await:别用 forEach
Array.prototype.forEach 不识别 async 回调,await 完全无效:
[1, 2, 3].forEach(async (id) => {
await save(id); // 不会按顺序执行
});
console.log('done'); // 立刻打印,根本没等
要顺序执行用 for...of:
for (const id of ids) {
await save(id);
}
要并发执行用 map + Promise.all:
await Promise.all(ids.map((id) => save(id)));
最后一点:顶层 await
ES2022 之后,ESM 模块顶层可以直接用 await,不用再包一层 async function main():
// index.mjs
const config = await fetch('/config.json').then((r) => r.json());
export default config;
但只在 .mjs 或 package.json 配了 "type": "module" 的项目里能用,CommonJS 的 .js 文件还是老老实实包 async 函数吧。