I recently came across a question that overturned my understanding of Promise.

The topic is as follows:

Promise.resolve().then(() = > {
  console.log(0);
  return Promise.resolve(4);
}).then((e) = > {
  console.log(e);
})
Promise.resolve().then(() = > {
  console.log(1);
}).then(() = > {
  console.log(2);
}).then(() = > {
  console.log(3);
}).then(() = > {
  console.log(5);
}).then(() = > {
  console.log(6);
})
Copy the code

As I understood it, the output values should be 0,1,2,3,5,6,4. Instead, the output values are: 0,1,2,3,4,5,6

~. [[0,[[[4]]]], [1, [2, [3, [5, [6]]]]]]] ~. [0,[[4]]]], [1, [2, [3, [5, [6]]]]]]] Join the microtask queue in turn. Execute them sequentially.




First, both the Promise static resolve method and the then method in the instance return a new Promise instance, that is, return new Promise();

Meanwhile, the resolve method inside a Promise instance calls that instance’s THEN method if the argument passed in is a Promise instance, which means a new layer of Promise instances is created, and then the successful callback in the THEN method executes if the argument is a Promise instance, The instance’s THEN method is also called directly, and a new layer of Promise instances is also generated

Then the then method stores the resolve method inside the new Promise instance into a state list of the current instance. Assuming a promise instance A calls then multiple times, the then method actually maintains a list inside A containing information about multiple Promise instances. When the list is looped, all tasks within it begin to join the microtask queue. However, the current instance calls the then method multiple times. This is actually the problem, and for those of you who know about chained calls, the internal normal is to return this. The then method of a Promise is a chain-supported invocation reached by returning a new Promise instance. So the chain calls result in tasks that are not added to the same layer list.

So, to make it easier to understand, let’s simplify the above problem as follows:

Promise.resolve().then(() = > {
  console.log(1);
}).then(() = > {
  console.log(3);
})
Promise.resolve().then(() = > {
  console.log(2);
}).then(() = > {
  console.log(4);
})
Copy the code

Resolve returns a new Promise instance, so two Promise instances are generated in a macro task. Outer Promise instance 2] then outer Promise instance 1 produces a list of [Promise instance 1, [Promise instance 3]] because it chain-calls the then methods twice. Similarly, the outer Promise instance 2 internally generates a list like [Promise Instance 2, [Promise Instance 4]]. The reason why this is nested, rather than tiled, is that the then method returns a new promise instance. That is, it says return new Promise() inside, instead of returning this inside, so the chained call to the then method doesn’t produce a tiled arraylist, it produces a nested list of layers.

In practice, we can understand it like this:

(function(){
    setTimeout(() = > {
        setTimeout(() = > {
            console.log(1)
            setTimeout(() = > {
                console.log(3)},0)},0)},0)
    setTimeout(() = > {
        setTimeout(() = > {
            console.log(2)
            setTimeout(() = > {
                console.log(4)},0)},0)},0)
})()
Copy the code

Resolve (4) creates a new Promise instance, so a layer of Promise instances is passed as a value to the success callback of the then method. So a new layer of promise instances is created, which causes the 4 to come after the 3. If return promise.resolve (4) is replaced with plain return 4, the result will look like this interleaved print.

The above analysis process is actually derived by implementing a simplified version of the Promise first. The specific implementation is as follows: The code implementation of Promise was prepared for the interview question during job hunting. When I saw the question, I thought about using it to try it. Finally, after adding the static resolve method, I found the answer was consistent with the actual, so I had the above analysis process. Finally, hasty writing, if there is a mistake, welcome to correct.

// If you still don't understand, you can use this simple version of Promise to combine the questions and interrupt the points where appropriate to explore by yourself
function isAObjAndHaveThen(value){
  return value && typeof value === 'object' && typeof value.then === 'function'
}
function runAsMicroTask(task){
  // Since the current topic does not involve other asynchronous macro tasks, the use of setTimeout does not conflict with the meaning of the topic
  setTimeout(task, 0)}class Promise {
  cbs = [];
  status = "pending";
  value = null;
  constructor(fn) {
    fn(this._resolve.bind(this), this._reject.bind(this));
  }
  _handle(cb){
    if(this.status === 'pending') {this.cbs.push(cb);
      return
    }
    const fn = cb[this.status === 'fulfilled' ? 'resolve' : 'reject']
    const fnCb = cb[this.status === 'fulfilled' ? 'onSuccess' : 'onError']
    console.log('cbs'.JSON.stringify(this.cbs))
    runAsMicroTask(() = > {
      fn(fnCb ? fnCb(this.value) : this.value)
    })
  }
  _resolve(value) {
    if(isAObjAndHaveThen(value)){
      runAsMicroTask(() = > {
        value.then(this._resolve.bind(this), this._reject.bind(this))})return
    }
    this.status = 'fulfilled'
    this.value = value
    this.cbs.forEach(cb= > this._handle(cb))
  }
  _reject(err) {
    this.status = 'rejected'
    this.value = err
    this.cbs.forEach(cb= > this._handle(cb))
  }
  then(onSuccess, onError) {
    return new Promise((resolve, reject) = > {
      this._handle({ onSuccess, onError, resolve, reject });
    });
  }
  static resolve(arg){
    return new Promise(res= >{ res(arg) }); }}Copy the code