Promise 异步执行机制
参考:
1. 值穿透 / 异常穿透
总结:使用
.catch时,会默认为没有指定失败回调的.then添加一个失败回调。catch所谓的"异常穿透"并不是"一次失败就触发 catch",而是一层一层传递下来。
异常穿透的前提条件:链上所有
.then都没有指定失败回调。如果.catch之前的.then都指定了失败回调,.catch就失去意义。
2. 由一道 bilibili 面试题引入
var date = new Date();
console.log(1, new Date() - date);
setTimeout(() => {
console.log(2, new Date() - date);
}, 500);
Promise.resolve().then(console.log(3, new Date() - date));
while (new Date() - date < 1000) {}
console.log(4, new Date() - date);
答案:
1 0
3 1
4 1000
2 1000
时间差可能因机器性能有微小差异。
3. Event Loop 基础
执行顺序:同步任务 → 微任务(microtask)→ 宏任务(macrotask)→ 下一个宏任务 → 微任务 → ...
- 微任务:
MutationObserver、Promise.then/catch、fetch API、process.nextTick(Node)、Object.observe(已废弃)等。 - 宏任务:
script整体、setTimeout、setInterval、setImmediate、I/O、UI rendering、postMessage、MessageChannel。
4. 由浅入深:经典题目解析
题目一:同步 Promise
var promise = new Promise((resolve, reject) => {
console.log(1);
resolve();
console.log(2);
});
promise.then(() => { console.log(3); });
console.log(4);
// 输出:1 2 4 3
构造函数同步执行;
resolve()之后then回调进微任务队列。
题目二:未 resolve 的 Promise
var promise = new Promise((resolve, reject) => { console.log(1); });
promise.then(() => { console.log(2); });
console.log(3);
// 输出:1 3
没有
resolve时then一直 pending,不会执行。
题目三:then 直接传入函数
var promise = new Promise((resolve, reject) => { console.log(1); });
promise.then(console.log(2));
console.log(3);
// 输出:1 2 3
console.log(2)作为参数立即执行,输出2;返回undefined作为then回调。
题目四:then 的值穿透
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log);
// 输出:1
then期望函数,非函数会被忽略,值穿透到下一环。
题目五:resolve + reject
var promise = new Promise((resolve, reject) => {
console.log(1);
resolve();
reject();
});
promise.then(() => { console.log(2); }).catch(() => { console.log(3); });
console.log(4);
// 输出:1 4 2
Promise 状态一旦确定(
resolve/reject)就不可再改变。
题目六:return 透传
Promise.resolve(1)
.then(res => { console.log(res); return 2; })
.catch(err => { return 3; })
.then(res => { console.log(res); });
// 输出:1 2
catch不触发时,then拿到上一个then的return值。
5. 同步 + Promise + setTimeout
题目一
setTimeout(() => { console.log(1); });
Promise.resolve().then(() => { console.log(2); });
console.log(3);
// 输出:3 2 1
微任务先于宏任务。
题目二
var promise = new Promise((resolve, reject) => {
console.log(1);
setTimeout(() => { console.log(2); resolve(); }, 1000);
});
promise.then(() => { console.log(3); });
promise.then(() => { console.log(4); });
console.log(5);
// 输出:1 5 2 3 4
多个
then注册多个回调,都会依次执行。
6. 回到开篇题
var date = new Date();
console.log(1, new Date() - date); // 同步
setTimeout(() => { console.log(2, new Date() - date); }, 500); // 宏任务
Promise.resolve().then(console.log(3, new Date() - date)); // 微任务(console.log 作为参数立即执行)
while (new Date() - date < 1000) {} // 同步阻塞
console.log(4, new Date() - date); // 同步
输出顺序:1 0 → 3 1 → 4 1000 → 2 1000。
7. 总结
- 同步任务立即执行。
- 微任务(
Promise.then、MutationObserver)在同步任务结束后、当前宏任务结束前清空。 - 宏任务(
setTimeout、I/O)每轮 Event Loop 取一个执行。 Promise.then期望函数,非函数参数会被忽略(值穿透)。- 异常穿透:链式调用未指定失败回调时,错误会沿链一路传递到最近的
catch。
来源整理自:我的有道云笔记



