Print the output of the following program:

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

The output of this problem is 123. Why not 132? Since I have always understood that Promise does not have asynchronous functionality, it just helps solve the problem of asynchronous callbacks, which is essentially the same as callbacks, so if we follow this idea, resolve should immediately then. But it’s not. SetTimeout?

If you add a promise to a promise:

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

The order of execution is 1243, and the second Promise will be made earlier than the first, so it’s also intuitively strange. Why?

There are many libraries for Promise implementation, such as jQuery deferred, and many that provide polyfills, such as ES6-Promise, Lie, etc. Their implementation is based on the Promise/A+ standard, which is also used by ES6 Promise.

In order to answer the execution order question above, it’s important to understand how promises are implemented, so it’s important to look at how those libraries are implemented, especially asynchronous promises that I mistakenly thought didn’t exist, because the last line of console.log(2) is not executed last, Then there must be some asynchronous mechanism like setTimeout for the above synchronized code to be executed asynchronously, so that it can be executed after the code is finished.

Of course, we’re not just trying to solve a problem, but we’re trying to understand the inner workings of promises. Readers who have the time and interest to do their own analysis can then go back and compare the analysis. Or you can follow the following ideas, use the mouse and keyboard to work with me.

The lie library is used here. The code is easier to read than ES6-Promise. NPM install:

npm install lieCopy the code

To run the code in the browser, prepare the following HTML:


       
<html>
<head>
    <meta charset="utf-8">
</head>
<body>
    <script src="node_modules/lie/dist/lie.js"></script>
    <script src="index.js"></script>
</body>
</html>Copy the code

The contents of index.js are as follows:

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

Print the Promise to make sure you’ve overwritten the original one, as follows:

Since we couldn’t break points with native promises, we needed to use a third-party library.

Let’s create a breakpoint at resolve(3) on line 4 to see how resolve is executed.

This will be a pity. The function will set the state of self to FULFILLED, and the outcome to the value passed in to resolve, which is 3. If the resolve pass is a Promise, it goes to the Promise chain at line 187 above, which we don’t consider here. Here self refers to a Promise object:

It mainly has three attributes — outcome, Queue, and state, where outcome is the result of resolve and state is the state of the Promise. As you can see in line 83, there are three states of the Promise:

var REJECTED = ['REJECTED'];
var FULFILLED = ['FULFILLED'];
var PENDING = ['PENDING'];Copy the code

The state is initialized pending in the Promise constructor (Rejected/pending) following line 89:

function Promise(resolver) {
  if (typeofresolver ! = ='function') {
    throw new TypeError('resolver must be a function');
  }
  this.state = PENDING;
  this.queue = [];
  this.outcome = void 0;
  if(resolver ! == INTERNAL) { safelyResolveThenable(this, resolver); }}Copy the code

As you can see from the call stack on the right, the resolver is triggered by the Promise constructor, that is, when you new Promise, the function passing the parameters will be executed, as shown in the following figure:

The function passed in supports two arguments, the resolve and reject callbacks:

let resolver = function(resolve, reject) {
    if (success) resolve();
    else reject();
};

new Promise(resolver);Copy the code

These two functions are defined internally by Promise, but you need to call its function inside your function to tell it when it succeeds and when it fails, so it can proceed to the next step. So these two function arguments are passed in, and they are Promise’s callback functions. How does Promise define and pass these two functions? It’s still where the breakpoint was, but let’s change the position of the call stack on the right:

The thenable function is passed to the resolver, and onSuccess and onError are resolved and reject, respectively. Handlers: If we call resolve, which is onSuccess, it calls 236 lines. Handlers: Resolve goes to our first break point, so let’s put it again:

We then set the state, outcome, and other properties of the current Promise object. We’re not going into the while loop at line 193, because queue is empty. This will be mentioned later.

Let’s set a break point at then:

What did then do? As shown below:

Then can pass two parameters: successful callback and failed callback. We pass it a successful callback, where the chart above is underlined. This is a big pity. Moreover, since state has been fulfilled in the resolver, it will execute the unwrap function and pass the success callback, as well as the outcome given by resolve (and the promise parameter, which is mainly used to return, to form the THEN chain).

The unwrap function is implemented like this:

Execute the success callback passed to Promise in THEN on line 167, passing the outcome.

This code is wrapped in the immediate function, which is the key to solving the Promise asynchrony problem. In node_modules, lie uses the immediate library, which can implement a nextTick function, which is executed immediately after the current logical unit synchronization is completed, equivalent to setTimeout 0, But it’s not implemented directly with setTimeout 0.

Let’s focus on how it implements a nextTick function. Immediate uses a scheduleDrain:

function immediate(task) {
  // Ignore this judgment for now
  if (queue.push(task) === 1&&! draining) { scheduleDrain(); }}Copy the code

The implementation logic in this scheduleDrain is implemented like this:

var Mutation = global.MutationObserver || global.WebKitMutationObserver;
var scheduleDrain = null;
{
  // Browser environment, IE11 support above
  if (Mutation) {
      // ...
  } 
  / / the Node. Js environment
  else if(! global.setImmediate &&typeofglobal.MessageChannel ! = ='undefined')}// Low browser version solutions
  else if ('document' in global && 'onreadystatechange' in global.document.createElement('script')) {}// Use setTimeout
  else {
    scheduleDrain = function () {
      setTimeout(nextTick, 0); }; }}Copy the code

It will have a compatibility judgment, using MutationObserver first, then script tags, which is supported by IE6, and then setTimeout 0 if nothing else.

The MutationObserver can be used to monitor the changes of DOM nodes, such as adding and deleting, attribute changes, etc. Immediate is implemented like this:

  if (Mutation) {
    var called = 0;
    var observer = new Mutation(nextTick);
    var element = global.document.createTextNode(' ');
    // Listen for changes to the node's data property
    observer.observe(element, {
      characterData: true
    });
    scheduleDrain = function () {
      // Let the data property change, toggle between 0/1,
      // Triggers the Observer to execute the nextTick function
      element.data = (called = ++called % 2);
    };
  }Copy the code

Register an Observer with the nextTick callback, and then create a DOM node, Element, that becomes an observer and observes its data property. When the nextTick function needs to be executed, scheduleDrain changes the data property, which triggers the observer’s callback nextTick. It is executed asynchronously, immediately after the current code unit is executed, but before setTimeout 0, that is, the first line of the following code is the last to be printed:

setTimeout((a)= > console.log(5), 0);
new Promise(resolve= > {
    console.log(1);
    resolve(3);
    // Promise.resolve().then(()=> console.log(4))
}).then(num= > {
    console.log(num)
});
console.log(2);Copy the code

At this point, we can answer the question why the code above is printed in the order 123 instead of 132. The first thing we can be sure of is that 1 is printed first, because after a new Promise is passed to the resolver, it will be executed synchronously. After resolve(3) is performed, the state of the current Promiser object is changed to complete and the outcome is recorded. Then jumps out and executes then, passing the successful callback passed to Immediate at nextTick. NextTick is executed asynchronously using Mutation, so 3 is printed after 2.

If you write a promsie inside a promise, 4 will be printed before 3 because the inside promise’s THEN executes before the outside promise’s THEN, meaning that its nextTick is registered first.

This basically explains the order in which promises are executed. This code pushes successful callbacks to a global array queue when executing immediate, whereas nextTick executes those callbacks in sequence, as shown in the following code:

function nextTick() {
  draining = true;
  var i, oldQueue;
  var len = queue.length;
  while (len) {
    oldQueue = queue;
    // Clear the queue
    queue = [];
    i = - 1;
    // Execute all current callbacks
    while (++i < len) {
      oldQueue[i]();
    }
    len = queue.length;
  }
  draining = false;
}Copy the code

The value of the variables should be set to true when the transaction is completed, and then to false when the transaction is complete.

function immediate(task) {
  if (queue.push(task) === 1&&! draining) { scheduleDrain(); }}Copy the code

Since JS is single-threaded, I don’t think this variable decision is necessary for agriculture. The other thing is, when queue is empty, push a variable in, and queue has only one element, so it returns 1. So if you’ve already pushed, you don’t need to trigger nextTick because the first push will execute all the queue callbacks, just make sure that subsequent operations are pushed into the queue. So this judgment is an optimization.

In addition, the core code of ES6-Promise is the same, except that it changes the immediate function to ASAP (as soon as possible), which also uses Mutation first.


Resolver code is synchronous, but we often use Promise in asynchronous cases. Resolve is asynchronous, not synchronous. For example:

let resolver = function(resolve) {
    setTimeout((a)= > {
        // Call resolve asynchronously
        resolve();
    }, 2000);
    // Resolve is not executed
};
new Promise(resolver).then(num= > console.log(num));Copy the code

The state of the Promise is still pending when executing then, and the code is 134 (unwrap 132) :

It creates a QueueItem and places it in the queue property of the current Promise object (note that queue and global queue are two different variables). Then asynchronously terminate the queue by calling resolve, where queue is not empty:

The success callback in the queue will be executed. Because THEN can be then multiple times, there can be multiple successful callbacks. This is also executed with the immediate call to nextTick.


That is to say if it is synchronous resolve, by way of such as MutationObserver/Setimeout 0 in the current code units performed immediately after the success callback; If resolve is asynchronous, the successful callback is placed on a queue of the current Promise object, and then the nextTick callback is invoked in the same way when resolve is executed.


We haven’t said a failed pullback yet, but it’s broadly similar.