"Promise 是异步的,await 是同步写法。"
这句话你可能听了上百次,但------你真的懂它背后的执行原理吗?
在这个性能与体验并重的时代,如果你还把 async/await 当作"写法更舒服的 Promise",那你可能错过了它真正的威力。
今天,我们就来一次彻底的拆解 。
Promise 与 async/await ------到底差在哪?为什么看起来同步的 await,依然是异步的?
一、Promise 是什么?本质是异步状态机
从规范角度看,Promise 就是一个异步状态机。它有三种状态:
| 状态 | 英文 | 含义 |
|---|---|---|
| 待定 | pending |
任务尚未完成 |
| 已兑现 | fulfilled |
成功完成,返回结果 |
| 已拒绝 | rejected |
发生错误,返回原因 |
一旦状态从 pending 变为 fulfilled 或 rejected,就不可逆。
js
const p = new Promise((resolve, reject) => {
setTimeout(() => resolve('done'), 1000);
});
p.then(value => console.log(value)); // done
二、事件循环基础:宏任务与微任务
在讲 Promise 前,必须先搞清楚 JS 的执行机制:事件循环(Event Loop) 。
🔹 1. 什么是宏任务(Macro Task)?
宏任务是指每次事件循环中执行的主任务块,包括:
script(整段代码)setTimeoutsetIntervalsetImmediate(Node.js)- I/O 操作 等
🔹 2. 什么是微任务(Micro Task)?
微任务是插入在一次宏任务结束之后、下一个宏任务开始之前 执行的任务。
常见的微任务包括:
Promise.thenqueueMicrotaskMutationObserver
🔹 3. 执行顺序总结:
- 执行一个宏任务;
- 执行完后,清空当前所有微任务;
- 再开始下一个宏任务。
text
┌──────────────────────────────────┐
│ 开始执行 JS 脚本(第一个宏任务) │
└───────────────┬──────────────────┘
│
▼
执行同步代码(主线程执行区)
│
▼
注册异步任务(定时器、Promise等)
│
▼
┌──────────────────────────────────┐
│ 宏任务执行完毕(本轮结束) │
└───────────────┬──────────────────┘
│
▼
检查微任务队列(Microtasks)
├─ Promise.then 回调
├─ async/await 恢复点
├─ queueMicrotask 等
│
▼
清空所有微任务(一次性执行完)
│
▼
┌──────────────────────────────────────────────┐
│ 开始执行下一个宏任务(下一轮事件循环) │
│ setTimeout、setInterval、I/O 等进入执行区 │
└──────────────────────────────────────────────┘
我们看一个例子:
js
console.log('A');
setTimeout(() => console.log('B'), 0); // 宏任务
Promise.resolve().then(() => console.log('C')); // 微任务
console.log('D');
输出顺序:
css
A
D
C
B
解释:
- 整个脚本是第一个宏任务;
Promise.then注册的回调进入微任务队列;- 宏任务(脚本)执行完后清空微任务 → 输出
C; - 最后再执行下一个宏任务(
setTimeout)→ 输出B。
三、then 的原理:注册微任务,异步回调
.then() 不会立刻执行,而是:
- 把成功/失败回调注册到微任务队列;
- 返回一个新的 Promise,用于链式调用。
javascript
Promise.resolve().then(() => console.log('microtask'));
console.log('sync');
// 输出:
// sync
// microtask
要点:
.then()的回调属于微任务,会在当前宏任务结束后立即执行。
四、async/await:Promise 的"同步写法"
async/await 是对 Promise 的高级封装。
async函数会自动返回一个 Promise;await让代码"暂停",等 Promise 完成后继续执行。
示例:
js
async function fetchData() {
const res = await fetch('/api/data');
const data = await res.json();
console.log(data);
}
等价于:
js
function fetchData() {
return fetch('/api/data')
.then(res => res.json())
.then(data => console.log(data));
}
结论:
await只是让异步代码"写起来像同步",但本质依旧是基于微任务的异步机制。
五、await 的底层执行逻辑
当 JS 引擎遇到 await 时,会这样做:
-
计算
await后的表达式; -
如果不是 Promise,直接返回值;
-
如果是 Promise:
- 暂停当前
async函数; - 把后续代码(
await之后的部分)放入微任务队列; - 等 Promise 完成后,恢复执行。
- 暂停当前
举例:
js
async function test() {
console.log(1);
await null;
console.log(2);
}
test();
console.log(3);
输出:
text
1
3
2
解释:
await null会被转换为Promise.resolve(null);- 当前函数暂停,后续
console.log(2)放入微任务; - 先执行同步代码
1、3,再执行微任务2。
六、Promise.then vs async/await 对比总结
| 对比点 | Promise.then() |
async/await |
|---|---|---|
| 写法风格 | 链式调用 | 同步风格 |
| 返回值 | 新的 Promise | 一个 Promise |
| 错误处理 | .catch() |
try...catch |
| 可读性 | 回调嵌套 | 线性逻辑 |
| 执行机制 | 微任务回调 | 微任务暂停+恢复 |
| 底层实现 | 回调链 | 状态机封装 Promise |
一句话总结:
await是把.then()的回调逻辑拆成了"暂停 + 恢复执行"两步。底层依然是 Promise 和微任务在驱动。
七、为什么有时 .then() 比 await 先执行?
javascript
async function main() {
const a = await Promise.resolve('A');
const b = await Promise.resolve('B');
Promise.resolve('C').then(console.log);
console.log(a, b);
}
main();
// 输出:
// C
// A B
解释:
.then()的回调会立即注册微任务;- 而
await的后续执行要等当前 await 完成后再注册; - 所以微任务队列中
.then的任务排在更前。
小结
很多人会背结论:
"await 会暂停函数","then 会注册回调"...
但要真正写出高性能、无陷阱的异步代码,
你必须理解它们在 事件循环(Event Loop) 中的真实运行逻辑,同样也是面试中的技术要点。
一句话复盘:
then是异步回调注册机制,
await是暂停 + 恢复的语法糖,它们的底层,都是 Promise 与微任务。