关于面试题中return Promise.resolve()引发的思考与探索~

351 阅读6分钟

1654422456782.png

起因是群友做到这样一道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函数回调不同的函数来返回结果。

之后我们简单的运行代码测试一下~运行结果是符合预期的

1654436505893.jpg

但是新的问题是不能链式调用then函数,所以我们需要继续修改

1654438442804.jpg

为了解决链式调用的问题,我们在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)
        }
    }
}

然后再次运行测试一下~链试调用就完成了

1654440474370.jpg

然后我们对promise的异步处理做一个测试

1654480926580.jpg

可以发现这里所有的代码都是同步执行的,因为我们代码实现上没有做任何异步处理,所以这里我们通过创建微任务的方法去修改

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);
      }
    }) 

之后代码进行测试~结果符合预期

1654481456336.jpg

最后实现一下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)
            })
        }
    }

继续测试,走你~

1654442915821.jpg

这里报错了,仔细检查发现是当判断value不属于Mingpromise实例后,我创建了新的Mingpromise实例,但是少写了return导致这里then是undefined。之后再次测试,结果达到预期~

1654479313733.jpg

我们的手写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;

1654481856257.jpg

对比和浏览器运行的结果,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也是参照这个实现的。

这里我们可以先对面试题进行一个小改动

1654485087341.jpg

当我们直接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的后面

1654486285930.jpg

最后我们来解释为什么浏览器打印的结果会是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+规范实现的,但是在此基础也上做了一些功能扩展。所以最后可能在某些时刻执行的结果会不一样。这就需要我们尽可能的去学习掌握更多代码底层的实现,这样不管题目怎么改变,我们都可以比较从容的去解决~