promise本身不被调度,其异步行为通过状态变更触发微任务入队实现;真正被调度的是.then()等回调,promise仅作为开关和容器。
promise 对象本身不直接“被调度”,它不主动往任务队列里塞东西;它的异步行为,是通过 状态变更触发微任务入队 来实现的。真正被调度的是 .then()、.catch() 这些回调函数,而 promise 是那个“开关”和“容器”。
Promise 状态变更决定微任务何时入队
一个 Promise 的 fulfilled 或 rejected 状态,必须由 resolve() 或 reject() 触发。这个触发时机,决定了其后续 .then() 回调什么时候被放进微任务队列:
- 如果
resolve('ok')是同步执行的(比如在new Promise((r) => r('ok'))中),那么该 Promise 立即进入fulfilled状态,.then()回调会立刻被推入微任务队列(但不会立即执行); - 如果
resolve('ok')是异步触发的(比如setTimeout(() => r('ok'), 0)),那状态变更发生在下一个宏任务开始前,.then()回调就等到那时才入微任务队列。
关键点:
- Promise 构造器里的 executor 函数是同步执行的;
-
resolve/reject调用本身是同步的,但它们内部会:- 更新 Promise 状态;
- 若有已注册的
.then回调,就用queueMicrotask把它们排进微任务队列。
微任务队列才是真正的“调度器”
JavaScript 引擎维护一个独立的 微任务队列(Microtask Queue),它和宏任务队列(如 setTimeout)分开管理:
- 每次宏任务(比如一次点击事件、
setTimeout回调)执行完后,引擎会:- 清空整个微任务队列(不是只取一个,而是全部执行完);
- 然后再取下一个宏任务。
所以 Promise 的 .then() 总是比 setTimeout 更早执行,哪怕 setTimeout(fn, 0) 写在前面。
例子:
console.log('1');
Promise.resolve().then(() => console.log('2'));
setTimeout(() => console.log('3'), 0);
console.log('4');
// 输出:1 → 4 → 2 → 3
这里 .then() 回调被放入微任务队列,setTimeout 回调被放入宏任务队列,调度顺序由事件循环规则保证。
race / all / any 等组合方法不改变调度本质
像 Promise.race()、Promise.all() 这些方法:
- 它们本身是同步函数,不创建微任务;
- 它们只是监听传入的 Promise,并在第一个完成时立即 settle 自己返回的新 Promise;
- 后续链上的
.then(),依然遵循标准微任务规则——等这个新 Promise 状态确定后,回调才进微任务队列。
例如:
Promise.race([Promise.resolve('a'), new Promise(r => setTimeout(r, 10))])
.then(v => console.log(v));
输出 'a' 的 .then 回调,和普通 Promise.resolve().then(...) 一样,属于本轮微任务。
手动控制调度:queueMicrotask
如果你需要让一段逻辑以微任务优先级执行,又不想绕一层 Promise,可以直接用:
queueMicrotask(() => {
console.log('这会在当前同步代码结束后、下一个宏任务前执行');
});
它和 Promise.resolve().then(...) 效果一致,但更轻量、语义更清晰。
不复杂但容易忽略











