Promise在前端中主要用于处理异步调用,其基本使用方式通过阮一峰大佬的文档一下就可以入手,但是最近我看了一篇文章wecTeam中,作者深山蚂蚁的《高级进阶:深度揭秘Promise注册微任务和执行过程》一文,让我对Promise的执行顺序有了更深的了解,与此同时我也有了一个疑问,通过这篇文章与大家探讨。
1. promise的异步主要发生在微任务队列中
2. 第一个then的回调监听最新Promise对象的resolve执行后才xx注册进微任务队列,之后的then回调都依赖于前一个then中的代码执行结束。
下面的内容主要基于两个概念讨论:
(1) 当前一个then中的代码都是同步执行的,执行结束后第二个then即可注册进入微任务队列。
(2) 当前一个then中有return 关键字,需要return的内容完全执行结束,第二个then才会注册进入微任务队列。
then也分下面几种情况:
// Case 1: 前一个then中的代码都是同步的
new Promise( (resolve) => { resolve();})
.then(() => {console.log(1);})
.then(() => {console.log(2)});
// 输出
// 1
// 2
// Case 2: 前一个then中的代码 return一个Promise对象
new Promise((r,rj) => {
console.log('外p');
r();
}).then(() => {
console.log('外then1');
new Promise(((r,rj) => {
console.log('内p');
r();
})).then(() => {
console.log('内then1');
return new Promise((r, rj) => {r();});
}).then(() => {
console.log('内then2');
});
}).then(() => {
console.log('外then2');
}).then(() => {
console.log('外then3');
}).then(() => {
console.log('外then4');
});
/**
输出结果:
"外p"
"外then1"
"内p"
"内then1"
"外then2"
"外then3"
"外then4"
"内then2"
**/
我自己疑惑的主要是Case2这种情况,为什么"内then2"会在"外then4"之后打印?
return new Promise((r, rj) => {r();}) 等同于
return new Promise((r, rj) => {r();}).then(()=>{console.log(1)}).then(()=>{console.log(2)}).then(()=>{console.log(3)});
这里面究竟藏着什么原因?
为什么Part one和Part two的打印结果是一样的?
------------- Part one ------------------------
new Promise(r => {
r(1)
}).then(r => {
console.log('p1 then1');
return new Promise(r => {
r(2);
});
}).then(r => {
console.log('p1 then2', r);
});
new Promise(r => {
r(1)
}).then(r => {
console.log('p2 then1');
}).then(r => {
console.log('p2 then2');
}).then(r => {
console.log('p2 then3');
}).then(r => {
console.log('p2 then4');
});
------------- Part two ------------------------
new Promise(r => {
r(1)
}).then(r => {
console.log('p1 then1');
return new Promise(r => {
r(2);
}).then(r => r);
}).then(r => {
console.log('p1 then2', r);
});
new Promise(r => {
r(1)
}).then(r => {
console.log('p2 then1');
}).then(r => {
console.log('p2 then2');
}).then(r => {
console.log('p2 then3');
}).then(r => {
console.log('p2 then4');
});
最新在研究Promise实现原理,弄清了then方法的代码实现后,对于以上今年年初记录这篇博客遗留的问题有了比较清晰的解释了。
废话不多说,直接上代码,下图就是then方法返回一个promise的对象时,会有一次空的微任务执行的“错觉”的原因
// 简化版then的实现
Promise.prototype.then = function(onFullfilled, onRejected) {
onFullfilled = typeof onFullfilled === 'function' ? onFullfilled : function (data) { return data; };
onRejected = typeof onRejected === 'function' ? onRejected : function (reason) { return reason; };
const self = this;
const promise2 = new MyPromise(function (resolve, reject) {
if (self.status === PENDING) {
self.resolveCallbacks.push(function () {
// 此处的setTimeout用来模拟将该处代码推入微任务队列
setTimeout(function() {
const x = onFullfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
}, 0);
});
self.rejectCallbacks.push(function () {
setTimeout(function() {
const x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
}, 0);
})
}
if (self.status === RESOLVED) {
setTimeout(function() {
const x = onFullfilled(self.value);
resolvePromise(promise2, x, resolve, reject);
}, 0);
}
if (self.status === REJECTED) {
setTimeout(function() {
const x = onRejected(self.reason);
resolvePromise(promise2, x, resolve, reject);
}, 0);
}
});
return promise2;
}
function resolvePromise (promise2, x, resolve, reject) {
if (promise2 === x) {
// 防止自己等待自己
return reject(new TypeError('循环引用了'));
}
let called; // 表示Promise有没有被调用过 // x是object或者是个function
if ((x !== null && typeof x === 'object') || typeof x === 'function') {
try {
let then = x.then;
// 如果返回的x是promise对象, 执行这个promise的then方法,递归resolvePromise将第二个
// 参数为非thenable为止
if (typeof then === 'function') {
then.call(x, function (y) {
if (called) {
// 是否调用过
return;
}
called = true;
resolvePromise (promise2, y, resolve, reject)
},
function (r) {
if (called) {
// 是否调用过
return;
}
called = true;
reject(r);
});
}
else {
// 当前then是一个普通对象。
resolve(x)
}
}
catch (e) {
if (called) {
// 是否调用过
return;
}
called = true;
reject(e);
}
}
else {
if (called) {
// 是否调用过
return;
}
called = true;
resolve(x);
}
}
我们结合一个例子来看
const p = new Promise(function(rs, rj) { rs(1); }) .then(r1 => {
console.log('r1', r1);
return new Promise(function(rs2, rj2){
rs2(2)
});
});
p.then(r2 => {
console.log('r2', r2);
})
new Promise(r => r(11))
.then(r11 => {
console.log('r11', r11);
return r11 + 1;
})
.then(r12 => {
console.log('r12', r12);
return r12 + 1;
})
.then(r13 => {
console.log('r13', r13);
});
深度的原因,推荐掘金大佬文章《【V8源码补充篇】从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节》、深度揭秘 Promise 微任务注册和执行过程 - 掘金, 从chrome浏览器实现Promise源码解析为什么then中return 一个Promise对象的时候,会注册两次微任务才会继续注册下一个then