Familiar with the Promise of

Say Promise presumably everyone is not unfamiliar with it, prop up the banner of JS asynchronous call. A Promise is a result that holds the result of an event (called asynchronously) that will happen in the future

Take a look at the basic use of Promise

const p1 = new Promise((resolve, reject) = > {
    console.log('Code that just enters the Promise and executes immediately')
    if(true) {
        resolve('nice !! ')}else {
        reject('some Error')
    }
})

p1.then(val= > {
    console.log(val)
}, err= > {
    console.log(err)
})
Copy the code

A Promise takes a function as an argument, This function has two parameters: resolve, reject. Resolve is to change the state of the Promise from pedding to fulfilled. Reject is to change the state of the Promise from pedding to fulfilled Let’s look at an example

const p2 = new Promise((resolve, reject) = > {
     console.log('enter the Promise')
     reject('error 1')
     resolve('resolve 1')
     resolve('resolve 2')
}).then(res= > {
    console.log(res, 'then1')
}).then(res= > {
    console.log(res, 'then2')
}).catch(err= > {
    console.log(err)
})

Copy the code

The output of this example from the console is as follows

The reason is that the state of a Promise is irreversible once it is resolved or rejected

So let’s go back to the code abovereject('error 1')Take it out and see what happensIt’s not surprising that then1 is undefined because then2 is undefined because the argument to the then method requires the result of the return in the previous THEN method, for example

How did the Promise come true

So let’s look at how promises are implemented internally (the following source code uses Promise A+). When A Promise is used, it is A new Promise. Obviously, A Promise is A constructor. Let’s write a simple Promise example to plug into the source code and see what happens

const p3 = new Promise((resolve, reject) = > {
    console.log('Just entered the Promise')
    resolve('resolve 1')
    resolve('resolve 2')
    reject('reject error')
}).then(res= > {
    console.log(res, 'then1')
    return 'then1 result'
}, err= > {
    console.log(err, 'then1 error')
}).then(res= > {
    console.log(res, 'then2')})Copy the code

The first thing I see is the Promise constructor, an input, fn, which is the body of the function that we pass the New Promise to, and I skip two judgments and you see that in the Promise constructor, the state of the Promise is initialized, and I’ve marked the comment in the code

function Promise(fn) {
  if (typeof this! = ='object') {
    throw new TypeError('Promises must be constructed via new');
  }
  if (typeoffn ! = ='function') {
    throw new TypeError('Promise constructor\'s argument is not a function');
  }
  this._deferredState = 0; // The state of the next call to the Promise tag is used in subsequent calls to the then method chain
  this._state = 0; // Promise state 0 pedding; 1 fulfilled; 2 rejected;
  this._value = null; The value of / / Promise
  this._deferreds = null; // For the next call, the Promise instance is used in subsequent calls to the then method chain
  if (fn === noop) return; // The noop function is an empty function that determines if the Promise is a new Promise in the then method. If the Promise is a new Promise in the THEN method, no subsequent direct return is executed
  doResolve(fn, this);
}
Copy the code

doResolve

There’s not a lot of things that we do in constructors, some judgments and some initialization, so let’s move on, let’s talk about parameters,

  1. The first argument, fn, is the body of the function we pass in to the New Promise,
  2. The second argument, this, is an instance of the current Promise.
function doResolve(fn, promise) {
  var done = false; // Initialize a marked variable
  var res = tryCallTwo(fn, function (value) {
    if (done) return;
    done = true;
    resolve(promise, value);
  }, function (reason) {
    if (done) return;
    done = true;
    reject(promise, reason);
  });
  if(! done && res === IS_ERROR) { done =true; reject(promise, LAST_ERROR); }}Copy the code

DoResolve () calls tryCallTwo with three arguments

  1. The first is the body of the function passed the New Promise
  2. The second argument is a function that executes the resolve method
  3. The third argument is a function that executes the reject method

tryCallTwo

Let’s look at tryCallTwo

function tryCallTwo(fn, a, b) {
  try {
    fn(a, b);
  } catch (ex) {
    LAST_ERROR = ex;
    returnIS_ERROR; }}Copy the code

He just executes fn with the two arguments a and b as the two arguments of fn, so a and B are the same as resolve and reject in the body of our new Promise function, and when we execute resolve or reject in the body of our new Promise function, So let’s just do this function a and b and let’s plug in our example

const p3 = new Promise((resolve, reject) = > {
    console.log('Just entered the Promise')
    resolve('resolve 1')
    resolve('resolve 2')
    reject('reject error')})function doResolve(fn, promise) {
  var done = false; // Initialize a marked variable
  var res = tryCallTwo(fn, function (value) { // value => 'resolve 1'
    if (done) return; // On the first call done is false
    done = true; // If done is set to true after the first call, a direct return of resolve or reject does not take effect
    resolve(promise, value);
  }, function (reason) {
    if (done) return;
    done = true; // For the same reason as above
    reject(promise, reason);
  });
  if(! done && res === IS_ERROR) {// Reject an exception
    done = true; reject(promise, LAST_ERROR); }}Copy the code

That’s why the Promise’s state is irreversible and what happens next

resolve

  1. The first parameter is an instance of the current Promise
  2. The second parameter is the value of the current Promise
function resolve(self, newValue) { // newValue => 'resolve 1'
  if (newValue === self) {
    return reject(
      self,
      new TypeError('A promise cannot be resolved with itself.')); }if (
    newValue &&
    (typeof newValue === 'object' || typeof newValue === 'function')) {var then = getThen(newValue);
    if (then === IS_ERROR) {
      return reject(self, LAST_ERROR);
    }
    if (
      then === self.then &&
      newValue instanceof Promise
    ) {
      self._state = 3;
      self._value = newValue;
      finale(self);
      return;
    } else if (typeof then === 'function') {
      doResolve(then.bind(newValue), self);
      return;
    }
  }
  self._state = 1;
  self._value = newValue;
  finale(self);
}
Copy the code

It is clear that our newValue is currently equal to the string ‘resolve 1’, and that neither of the two judgments is consistent with assigning either _state or _value directly to the current Promise instance. Next, perform finale

finale

The instance of the current Promise has a _state of 1, _value of ‘resolve 1’, _deferreds of NULL, and _deferredState of 0, so both judgments are skipped

function finale(self) {
  if (self._deferredState === 1) {
    handle(self, self._deferreds);
    self._deferreds = null;
  }
  if (self._deferredState === 2) {
    for (var i = 0; i < self._deferreds.length; i++) {
      handle(self, self._deferreds[i]);
    }
    self._deferreds = null; }}Copy the code

then

At this point, the Promise has been executed, but isn’t that a Promise a microtask? So far we haven’t seen anything to do with microtasks, so what about the execution of the then method

const p3 = new Promise((resolve, reject) = > {
    console.log('Just entered the Promise')
    resolve('resolve 1')
}).then(res= > {
    console.log(res, 'then1')
    return 'then1 result'
}, err= > {
    console.log(err, 'then1 error')})Copy the code
function noop() {} // An empty function declared at the top of the source code

Promise.prototype.then = function(onFulfilled, onRejected) {
  if (this.constructor ! = =Promise) {
    return safeThen(this, onFulfilled, onRejected);
  }
  var res = new Promise(noop);
  handle(this.new Handler(onFulfilled, onRejected, res));
  return res;
};
Copy the code

If this is not a Promise, it must be a Promise and an instance of the Promise is passed to noop

 if (fn === noop) return;
 doResolve(fn, this);
Copy the code

So doResolve will not continue after a return and the Promise’s state will not change

handle

So let’s go ahead and execute the handle method, so we have a new Handler here, so what’s going on inside that Handler

function Handler(onFulfilled, onRejected, promise){
  this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
  this.onRejected = typeof onRejected === 'function' ? onRejected : null;
  this.promise = promise;
}
Copy the code

That is, we assign the argument passed to the THEN and the new Promise instance to the constructor

  1. The first parameter is an instance of the current Promise
  2. The second argument is an instance of the new Handler
function handle(self, deferred) { //self => { _state : 1, _value: 'resolve 1', _deferredState: 0, _deferreds: null }
  while (self._state === 3) {
    self = self._value;
  }
  if (Promise._onHandle) { // The initialization is null
    Promise._onHandle(self);
  }
  if (self._state === 0) { 
    if (self._deferredState === 0) {
      self._deferredState = 1;
      self._deferreds = deferred;
      return;
    }
    if (self._deferredState === 1) {
      self._deferredState = 2;
      self._deferreds = [self._deferreds, deferred];
      return;
    }
    self._deferreds.push(deferred);
    return;
  }
  handleResolved(self, deferred);
}
Copy the code

Since the current Promise has a _state value of 1 (set in the resolve method), both the judgment and the loop skip directly executing the handleResolve

handleResolve

The first argument is an instance of the current Promise instance and the second argument is a new Handler

function handleResolved(self, deferred) {
  asap(function() {
    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
    if (cb === null) {
      if (self._state === 1) {
        resolve(deferred.promise, self._value);
      } else {
        reject(deferred.promise, self._value);
      }
      return;
    }
    var ret = tryCallOne(cb, self._value);
    if (ret === IS_ERROR) {
      reject(deferred.promise, LAST_ERROR);
    } else{ resolve(deferred.promise, ret); }}); }Copy the code

All right, here… Finally, you see something about asynchronous calls. It was here for a long time. The ASAP method is referenced to an external package

asap

var asap = require('asap/raw');
Copy the code

The main thing that this package does is to create a microtask, and then push things from the THEN method to the microtask queue for the queue to execute. Let’s look at the code implementation of the push to the microtask queue at the core of this method

var BrowserMutationObserver = scope.MutationObserver || scope.WebKitMutationObserver;
var requestFlush = makeRequestCallFromMutationObserver(flush);

module.exports = rawAsap;
function rawAsap(task) {
    if(! queue.length) { requestFlush(); flushing =true;
    }
    // Equivalent to push, but avoids a function call.
    queue[queue.length] = task;
}

function makeRequestCallFromMutationObserver(callback) {
    var toggle = 1;
    var observer = new BrowserMutationObserver(callback);
    var node = document.createTextNode("");
    observer.observe(node, {characterData: true});
    return function requestCall() {
        toggle = -toggle;
        node.data = toggle;
    };
}
Copy the code

The e browser environment Promise uses the MutationObserver API to push microtasks. All the way here, a promised regular link is gone. Tasks pushed into the microtask queue wait for the event loop to execute, and the asap method callback function is executed when the execution is complete

function handleResolved(self, deferred) {
  asap(function() {
    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected; _state = 1; cb is not null
    if (cb === null) {
      if (self._state === 1) {
        resolve(deferred.promise, self._value);
      } else {
        reject(deferred.promise, self._value);
      }
      return;
    }
    var ret = tryCallOne(cb, self._value); // Here is the body of the function that executes the THEN
    if (ret === IS_ERROR) {
      reject(deferred.promise, LAST_ERROR);
    } else{ resolve(deferred.promise, ret); }}); }Copy the code

After two judgments are skipped, the tryCallOne method is executed

tryCallOne

  1. The callback in the first argument then
  2. The second parameter is the Value of the current Promise
function tryCallOne(fn, a) {
  try {
    return fn(a);
  } catch (ex) {
    LAST_ERROR = ex;
    returnIS_ERROR; }}Copy the code

You can see here that we execute the current Promise as an input to the then method callback and return the result of that function and then execute the resolve method, Now, the incoming arguments here notice that the first one is passed to the next Promise instance of the THEN method and the second one is the result of the current then method execution up here, again, skip the judgment, assign _state, _value and then execute the finale method, However, code within _deferredState 0 finale still does not execute. At this point our simple case has been executed.

summary

A little summary

  1. The code in the body of the Promise function is executed immediately, and async code is executed only when it hits resolve or reject
  2. The state of a Promise is irreversible. After the first resolve or reject state is changed, the later resolve and reject states do not take effect
  3. A Promise is a then method that pushes events to the microtask queue
  4. The browser side of the Promise is a microtask queue pushed through the MutationObserver API
  5. Callbacks in then methods take the value of the value passed in by resolve or reject, and callbacks in chained THEN methods take the result of the return in the previous THEN

The Promise of chained calls

We know that Promise can be called in a chain, from the then method in the source code, we can see that a new Promise instance returns, so we can be continuous. Take a look at the following Promise

const p1 = new Promise((resolve, reject) = > {
    resolve('p1 resolve')
}).then(() = > {
    console.log('p1 then1')
}).then(() = > {
    console.log('p1 then2')
}).then(() = > {
    console.log('p1 then3')})const p2 = new Promise((resolve, reject) = > {
    resolve('p2 resolve')
}).then(() = > {
    console.log('p2 then1')
}).then(() = > {
    console.log('p2 then2')
}).then(() = > {
    console.log('p2 then3')})Copy the code

I don’t know if the execution results are the same as you think, but let’s analyze the source code to explain why the execution results are cross.

We here above the analysis of the code will not stick to the source code, direct method name with, unfamiliar students can look up.

  1. Initialization within the Promise constructor
this._deferredState = 0
this._state = 0;
this._value = null;
this._deferreds = null;
Copy the code
  1. performdoResolve.tryCallTwo(Done = true to make resolve or reject impossible)
  2. performresolveSets the status and assigns a value to the current Promise
this._state = 1;
this._value = newValue // 'p1 resolve'
Copy the code
  1. performfinale
  2. performthen
  3. performnew Handler,handle
  4. performhandleResolvedThis is where asap is used to push the body of the function in THEN into the microtask queue. Currently, the microtask queue is [Promise(fulfilled)], a microtask
  5. Next execution is behind then method, rather than the method in the callback function asap, because the browser event loop mechanism, this I will another article, summarizing down is the browser after the execution of the master station synchronization code execution of the current main task in the queue the task under the stack, queue execution is first in first out.
  6. So it executes the next one. Then I’ll post the code here
Promise.prototype.then = function(onFulfilled, onRejected) {
  if (this.constructor ! = =Promise) {
    return safeThen(this, onFulfilled, onRejected);
  }
  var res = new Promise(noop);
  handle(this.new Handler(onFulfilled, onRejected, res));// This refers to the new Promise instance that returns in the first THEN method
  return res;
};
Copy the code
  1. performthen
  2. performhandle
    1. The Promise instance returned in the (first) THEN before the first argument
    2. The second argument is an instance of the new Handler
function handle(self, deferred) { // _state: 0, _value: null, _deferredState: 0, _deferreds: null
  while (self._state === 3) {
    self = self._value;
  }
  if (Promise._onHandle) {
    Promise._onHandle(self);
  }
  if (self._state === 0) { // This is where the judgment comes in
    if (self._deferredState === 0) { // By judgment
      self._deferredState = 1;
      self._deferreds = deferred;
      return;
    }
    if (self._deferredState === 1) {
      self._deferredState = 2;
      self._deferreds = [self._deferreds, deferred];
      return;
    }
    self._deferreds.push(deferred);
    return;
  }
  handleResolved(self, deferred);
}
Copy the code

HandleResolved will not be executed, since self is the previous Promise instance returned in then, and _state is initialized to 0 so it enters the judgment. Anything that enters the judgment will return, so handleResolved is not executed

We know that the asap method called in the handleResolved method pushes the body of the function in the THEN to the microtask queue, so since it is not executed, the body of the subsequent then method is not pushed to the microtask queue, it is just marked (_deferredState).

In the same way, the next then method does not push the task to the microtask queue, it is just marked, so the Promise const P1 will only push one microtask to the microtask queue So after the two Promises are executed there are only two microtasks in the microtask queue and then the tasks in the microtask queue (asap callback) are executed with the first Promise instance (the first Promise instance) and the second parameter new Handler instance (where the promise property is stored as the first promise instance used by the THEN)

function handleResolved(self, deferred) {
  asap(function() {
    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
    if (cb === null) {
      if (self._state === 1) {
        resolve(deferred.promise, self._value);
      } else {
        reject(deferred.promise, self._value);
      }
      return;
    }
    var ret = tryCallOne(cb, self._value);
    if (ret === IS_ERROR) {
      reject(deferred.promise, LAST_ERROR);
    } else{ resolve(deferred.promise, ret); }}); }Copy the code

In this case, when we execute resolve, we pass in the first Promise instance that was used in then, and we’ve already marked the _deferredState attribute 1 with the _state:0 in handle So the assignment after the resolve method executes and the finale method executes after the state modification begins to execute

function finale(self) {
  if (self._deferredState === 1) {
    handle(self, self._deferreds);
    self._deferreds = null;
  }
  if (self._deferredState === 2) {
    for (var i = 0; i < self._deferreds.length; i++) {
      handle(self, self._deferreds[i]);
    }
    self._deferreds = null; }}Copy the code

The handleResolved task will be pushed into the microtask queue. The first task will be kicked out of the queue. The first task will be kicked out of the queue. [‘p2 resolve’, ‘p1 then1’] [‘p2 resolve’, ‘p1 resolve’] [‘p2 resolve’, ‘p1 then1’] [‘p1 then1’, ‘p2 then1’] [‘p1 then1’, ‘p2 then1’] [‘ P1 then1’, ‘P1 then2’]…

summary

A Promise is a microtask that is pushed in a THEN. Instead of pushing all tasks to the microtask queue, a chained call will only push the first THEN, and the body of the subsequent THEN method will be marked in the previous Promise property. [‘p1 resolve’, ‘p2 resolve’] [‘p2 resolve’, ‘p1 then1’] [‘p1 then1’] [‘p1 then1’] [‘p1 then1’] ‘p2 then1’] [‘p2 then1’, ‘p1 then2’] [‘p1 then2’, ‘p2 then2’] [‘p2 then2’, ‘p1 then3’] [‘p1 then3’, ‘p2 then3’] [‘p2 then3’] []