async/await 的原理是什么?和 Promise 有什么关系?
简化版
async/await 是 Promise 的语法糖。async 函数总是返回 Promise,await 会等待 Promise 结果,并让后续代码以微任务形式继续执行。它让异步代码看起来像同步流程,但不会阻塞主线程。
详细版
示例:
async function load() {
try {
const user = await fetchUser();
return user.name;
} catch (err) {
console.error(err);
}
}
等价于基于 Promise 的链式流程。await 后如果是普通值,会被包装成 resolved Promise;如果 Promise rejected,会抛出异常,可用 try/catch 捕获。
注意:多个互不依赖的异步任务不要顺序 await,应该用 Promise.all 并发。
完整版教学
一、async 函数返回 Promise
无论你在 async 函数里返回什么,外面拿到的都是 Promise:
async function f() {
return 1;
}
f().then(console.log); // 1
如果函数内部抛错,返回的是 rejected Promise。
二、await 不阻塞主线程
await 会暂停当前 async 函数的后续执行,把后续逻辑放到微任务里等待 Promise 完成。它不会阻塞整个 JS 主线程,其他同步代码仍然可以继续执行。
async function f() {
console.log(1);
await Promise.resolve();
console.log(2);
}
f();
console.log(3);
// 1 3 2
三、错误处理更接近同步代码
Promise 链用 .catch,async/await 可以用 try/catch:
try {
await request();
} catch (e) {
showError(e);
}
这让多步异步流程更好读。
四、避免不必要的串行
错误写法:
const a = await fetchA();
const b = await fetchB();
如果二者无依赖,会浪费时间。更好:
const [a, b] = await Promise.all([fetchA(), fetchB()]);
五、面试追问与工程落地
async/await 常见追问是“await 后面的代码是同步还是异步”。await 前同步执行,await 后会在 Promise settle 后继续,通常进入微任务队列。这就是为什么 await Promise.resolve() 后面的日志会晚于当前栈里的同步日志。
还会问“循环里能不能 await”。如果任务必须按顺序执行,比如后一项依赖前一项结果,可以用 for...of + await;如果任务互相独立,应该先创建 Promise,再 Promise.all 并发,否则会把并发请求写成串行请求。
工程中还要处理并发失败策略。Promise.all 任何一个失败就整体失败;如果希望拿到所有结果,无论成功失败,可以用 Promise.allSettled。这在批量上传、批量请求看板数据时更合适。
六、用时间线判断执行顺序与并发
await expression 会先求值表达式并按 Promise 语义吸收结果,然后暂停当前 async 函数;函数之外的同步代码仍继续执行。即便等待的是数字 1,await 后面的续体也不会在当前同步调用栈中立即执行,而是作为异步任务继续。回答输出顺序题时,应按“同步代码 → 已入队的微任务”逐行推演。
console.log('A')
async function run() {
console.log('B')
await 1
console.log('C')
}
run()
Promise.resolve().then(() => console.log('D'))
console.log('E')
// A、B、E、C、D
并发题还要区分“创建 Promise”和“等待 Promise”。假设两个互不依赖的请求各耗时约 100 ms,连续两次 await request() 总耗时约 200 ms;先创建两个 Promise 再 await Promise.all,理想总耗时约 100 ms。这里的数字用于解释关键路径,不代表网络请求一定精确按时结束。
| 写法 | 启动关系 | 失败表现 | 适用条件 |
|---|---|---|---|
连续 await | 前一个完成后再启动 | 在当前点抛出 | 后者依赖前者 |
Promise.all | 任务先并发启动 | 任一拒绝即整体拒绝 | 全部都必须成功 |
Promise.allSettled | 任务先并发启动 | 收集每项状态 | 允许部分失败 |
“await 不阻塞线程”只说明 JavaScript 执行栈会让出;当前 async 函数本身仍暂停,错误的串行写法照样会拉长业务耗时。
七、常见误区与追问
- 误区:async 函数只有写了 await 才返回 Promise。 async 函数每次调用都会立即返回一个 Promise,普通返回值会成为该 Promise 的兑现结果。
- 误区:await 会阻塞浏览器主线程。 它只暂停当前 async 函数,调用栈释放后其他同步代码和后续任务仍可运行。
- 误区:循环中出现 await 就一定正确。
for...of中逐次 await 会串行执行;是否正确取决于任务间有没有依赖和限流要求。 - 追问:await 一个普通值会同步继续吗? 不会,后续代码仍异步恢复,因此它可以改变日志输出顺序。
- 追问:async 函数 return 一个 Promise 会返回同一个对象吗? 返回的新 Promise 会跟随该 Promise 的状态,但对象身份通常不同。
- 追问:Promise.all 失败后其他请求会自动取消吗? 不会,它只提前拒绝聚合结果;取消底层任务需要
AbortController等额外机制。 - 追问:try/catch 能捕获没有 await 的 Promise 拒绝吗? 不能捕获已经直接返回或遗漏等待的异步拒绝;需要
await、returnPromise 或在 Promise 链上处理。
八、加强记忆
async/await 不是新异步模型,而是 Promise 的舒适写法。async 包 Promise,await 等结果,错误用 try/catch,独立任务用 Promise.all 并发。
遇到执行顺序题先标出每个暂停点,遇到性能题再判断 Promise 是何时创建的,就能避免把语法顺序误当作任务启动顺序。