起因是群友做到这样一道promsise面试题,由于好久没有接触promise面试题了,所以果断找浏览器大哥帮忙,打印结果是0123456。这个结果有点迷,如果按照等待Promise.resolve(4)执行完,并将结果和状态同步给外层的promise的思路,只需要多添加一次微任务就行了,但是这里明显是添加了两次微任务。于是我带着好奇探索了一个晚上,最终决定将所得收获分享给大家~
为了方便最后得出结果进行比较分析,这里我们自己先参考Promise A+规范写一个promise。
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
class Mingpromise{
constructor(executor){
//这里如果直接调用,this指向将会是Window或undefined
executor(this.resolve,this.reject)
}
status=PENDING
value=null
reason=null
//then方法可以多次被调用,当是异步调用时,我们使用数组来进行存储。之后通过循环遍历来调用回调
onFulfilledCallback=[]
onRejectedCallback=[]
reject = (reason)=>{
if(this.status===PENDING){
this.status=REJECTED
this.reason=reason
this.onRejectedCallback.forEach(Fn=>{
Fn(reason)
})
}
}
resolve = (value)=>{
if(this.status===PENDING){
this.status=FULFILLED
this.value=value
this.onFulfilledCallback.forEach(Fn=>{
Fn(value)
})
}
}
then(onFulfilled,onRejected){
if(this.status===FULFILLED){
onFulfilled(this.value)
}else if(this.status===REJECTED){
onRejected(this.reason)
//当promise中加入异步操纵的时候,往往then函数获取不到正确的状态,我们采用缓存回调函数来解决
}else if(this.status===PENDING){
this.onFulfilledCallback.push(onFulfilled)
this.onRejectedCallback.push(onRejected)
}
}
}
module.exports = Mingpromise
这里大致的讲一下基本原理,Promise是一个类,执行时接收一个执行器,且这个执行器会立即执行。当向执行器传入函数resolve或者reject后会改变Promise的内部状态,这个状态是不可二次修改的。完成状态修改之后就是对结果的管理,根据不同的状态,通过then函数回调不同的函数来返回结果。
之后我们简单的运行代码测试一下~运行结果是符合预期的
但是新的问题是不能链式调用then函数,所以我们需要继续修改
为了解决链式调用的问题,我们在then函数内创建一个新的Mingpromise然后return出去,同时设置一个变量去接收成功回调函数的结果,然后传入到PromiseResolve函数进行处理。(如果结果是Mingpromise的实例对象,我们则通过then方法去改变其内部状态)
then(onFulfilled,onRejected){
return new Mingpromise((resolve,reject)=>{
if(this.status===FULFILLED){
const res = onFulfilled(this.value)
this.PromiseResolve(res,resolve,reject)
}else if(this.status===REJECTED){
onRejected(this.reason)
}else if(this.status===PENDING){
this.onFulfilledCallback.push(onFulfilled)
this.onRejectedCallback.push(onRejected)
}
})
}
PromiseResolve(res, resolve, reject){
if(res instanceof Mingpromise){
res.then(resolve,reject)
}else{
resolve(res)
}
}
}
然后再次运行测试一下~链试调用就完成了
然后我们对promise的异步处理做一个测试
可以发现这里所有的代码都是同步执行的,因为我们代码实现上没有做任何异步处理,所以这里我们通过创建微任务的方法去修改
then(onFulfilled, onRejected) {
return new MingPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
queueMicrotask(() => {
const res = onFulfilled(this.value);
PromiseResolve(res, resolve, reject);
})
}
const rejectedMicrotask = () => {
queueMicrotask(() => {
const res = onRejected(this.reason);
PromiseResolve(res, resolve, reject);
})
}
if (this.status === FULFILLED) {
fulfilledMicrotask()
} else if (this.status === REJECTED) {
rejectedMicrotask()
} else if (this.status === PENDING) {
this.onFulfilledCallback.push(fulfilledMicrotask);
this.onRejectedCallback.push(rejectedMicrotask);
}
})
之后代码进行测试~结果符合预期
最后实现一下promise面试题里面用到的Promise.resolve和Promise.reject的静态方法
static resolve(value){
if(value instanceof Mingpromise){
return value
}else{
return new Mingpromise((resolve)=>{
resolve(value)
})
}
}
static reject(reason){
if(reason instanceof Mingpromise){
return reason
}else{
return new Mingpromise((reject)=>{
reject(reason)
})
}
}
继续测试,走你~
这里报错了,仔细检查发现是当判断value不属于Mingpromise实例后,我创建了新的Mingpromise实例,但是少写了return导致这里then是undefined。之后再次测试,结果达到预期~
我们的手写promise只进行到这里。我将整理后的代码放置在这里,接下来我们使用手写的promsie进行测试
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MingPromise {
constructor(executor){
executor(this.resolve, this.reject)
}
status = PENDING;
value = null;
reason = null;
onFulfilledCallbacks = [];
onRejectedCallbacks = [];
resolve = (value) => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach(Fn=>{
Fn(value)
})
}
}
reject = (reason) => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(Fn=>{
Fn(reason)
})
}
}
then(onFulfilled, onRejected) {
return new MingPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
queueMicrotask(() => {
const res = onFulfilled(this.value);
PromiseResolve(res, resolve, reject);
})
}
const rejectedMicrotask = () => {
queueMicrotask(() => {
const res = onRejected(this.reason);
PromiseResolve(res, resolve, reject);
})
}
if (this.status === FULFILLED) {
fulfilledMicrotask()
} else if (this.status === REJECTED) {
rejectedMicrotask()
} else if (this.status === PENDING) {
this.onFulfilledCallbacks.push(fulfilledMicrotask);
this.onRejectedCallbacks.push(rejectedMicrotask);
}
})
function PromiseResolve( res, resolve, reject) {
if(res instanceof MingPromise) {
res.then(resolve, reject)
} else{
resolve(res)
}
}
}
static resolve (parameter) {
if (parameter instanceof MingPromise) {
return parameter;
}else{
return new MingPromise(resolve => {
resolve(parameter);
});}
}
static reject (reason) {
return new MingPromise((reject) => {
reject(reason);
});
}
}
module.exports = MingPromise;
对比和浏览器运行的结果,0123456和0124356,可以发现浏览器运行的时候对于return Promise.resolve(4)是创建了两次微任务的操纵的.而我们自己实现的promise只有一次创建微任务。我在百度上查阅了很多资料。这里我们先查看Promise A+的实现
Promise.resolve = function (value) {
if (value instanceof Promise) return value;
if (value === null) return NULL;
if (value === undefined) return UNDEFINED;
if (value === true) return TRUE;
if (value === false) return FALSE;
if (value === 0) return ZERO;
if (value === '') return EMPTYSTRING;
if (typeof value === 'object' || typeof value === 'function') {
try {
var then = value.then;
if (typeof then === 'function') {
return new Promise(then.bind(value));
}
} catch (ex) {
return new Promise(function (resolve, reject) {
reject(ex);
});
}
}
return valuePromise(value);
};
在执行了Promise.resolve()之后将创建一个Promise实例,将实例状态设置为resolve状态,这个Promise.resolve()是同步实现的,所以它不会影响其他的异步操纵。而我们手写的promise也是参照这个实现的。
这里我们可以先对面试题进行一个小改动
当我们直接return 4 时,根据我们代码的实现,变量res会接收这个4,并传入PromiseResolve函数,进行类型判断不是promsie对象后直接resolve出去。这样整个过程中只创建了一次微任务,所以4会打印在1的后面。
....
return new MingPromise((resolve, reject) => {
const fulfilledMicrotask = () => {
queueMicrotask(() => {
const res = onFulfilled(this.value);
PromiseResolve(res, resolve, reject);
})
}
....
function PromiseResolve( res, resolve, reject) {
if(res instanceof MingPromise) {
res.then(resolve, reject)
} else{
resolve(res)
}
}
当我们return Promise.resolve(4)时,同样的流程,但是这次类型判断是属于promise对象,然后调用他的then方法完成状态的改变,这个过程中也相当于又创建了一次微任务。所以我们这次得到运行的结果是4会打印在2的后面
最后我们来解释为什么浏览器打印的结果会是0123456,这里我们同样查看浏览器(webkit)内部的实现代码
....
void Promise::Resolver::Resolve(Handle<Value> value) {
i::Handle<i::JSObject> promise = Utils::OpenHandle(this);
i::Isolate* isolate = promise->GetIsolate();
LOG_API(isolate, "Promise::Resolver::Resolve");
ENTER_V8(isolate);
EXCEPTION_PREAMBLE(isolate);
i::Handle<i::Object> argv[] = { promise, Utils::OpenHandle(*value) };
has_pending_exception = i::Execution::Call(
isolate,
isolate->promise_resolve(),
isolate->factory()->undefined_value(),
arraysize(argv), argv,
false).is_null();
EXCEPTION_BAILOUT_CHECK(isolate, /* void */ ;);
}
....
....
....
....
PromiseResolve = function PromiseResolve(promise, x) {
PromiseDone(promise, +1, x, promiseOnResolve)
}
function PromiseDone(promise, status, value, promiseQueue) {
if (GET_PRIVATE(promise, promiseStatus) === 0) {
PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue), status);
PromiseSet(promise, status, value);
}
}
运行在我们代码里体现的过程为0,return Promise.resolve(4),1依次进入微任务队列。执行到return Promise.resolve(4)时,创建一个Promise实例,执行其resolve方法,并将Promise状态修改为resolve。之后接着执行了打印1并将2添加到微任务队列的操纵,接着执行return的resolve(4)的promise,执行完成,整个return任务完成。之后接着执行打印2并将3添加到微任务队列。这个时候resolve(4)执行完成,然后将4添加到微任务队列,再然后执行打印3将5添加到微任务队列。最后打印出来的结果也就是0123456了
通过此次探索我们应该明白不同地方的promise,他们虽然是遵循Promise A+规范实现的,但是在此基础也上做了一些功能扩展。所以最后可能在某些时刻执行的结果会不一样。这就需要我们尽可能的去学习掌握更多代码底层的实现,这样不管题目怎么改变,我们都可以比较从容的去解决~