preface

What is Promise?

For those of you working on the front end who have used Promises, ES6 offers a powerful solution to the problems of asynchronous programming (hellbacks, etc.) : Promises. Promise is a constructor that transforms an asynchronous task into a synchronous one, changing the state of the task through resolve, reject, and the essential then methods used to accept the Promise value are all basic uses of promises. How do promises handle states, resove, Reject, and chain calls? If you don’t know, this article will help you. Let’s take a look at how promises are implemented. Everyone can write their own Promise method.

Here introduced making A in line with the Promise of A + standard source https://github.com/then/promiseCopy the code

Function object Promise

Let’s start with the SRC /index.js file

Some necessary definitions

// Define an empty function
function noop() {}

// Used to store error information
var IS_ERROR = {}

// Get the instance's then
function getThen(obj) {
  try {
    return obj.then;
  } catch (ex) {
    LAST_ERROR = ex;
    returnIS_ERROR; }}// Execute the then method callback
function tryCallOne(fn, a) {
  try {
    return fn(a);
  } catch (ex) {
    LAST_ERROR = ex;
    returnIS_ERROR; }}// Execute the Promise constructor
function tryCallTwo(fn, a, b) {
  try {
    fn(a, b);
  } catch (ex) {
    LAST_ERROR = ex;
    returnIS_ERROR; }}Copy the code

Promise constructor

function Promise(fn) {
  // Verify the instance
  if (typeof this! = ='object') {
    throw new TypeError('Promises must be constructed via new');
  }
  // Validates the Promise constructor
  if (typeoffn ! = ='function') {
    throw new TypeError('Promise constructor\'s argument is not a function');
  }
  // The stored instance status 0 indicates that the instance is not stored yet. 1 indicates that one instance is stored. 2 indicates that two instances are stored
  this._deferredState = 0;
  // The state 0 represents padding, which is a big pity, which is Fulfilled. The state 0 represents padding, which is Fulfilled. The state 0 represents Rejected, which is Fulfilled
  this._state = 0;
  / / the value of the Fulfilled
  this._value = null;
  // Store the instance after calling THEN
  this._deferreds = null;
  if (fn === noop) return;
  // Handle the Promise arguments
  doResolve(fn, this);
}
// No explanation
newPromise._onHandle = null;
newPromise._onReject = null;
newPromise._noop = noop;
Copy the code

When you instantiate a Promise with the new operator, you must pass in the Promise constructor fn, otherwise an error will be thrown, the state and value of the instance will be initialized, and the doResolve method will be called

DoResolve method

function doResolve(fn, promise) {
  // done to prevent repeated triggering
  var done = false;
  TryCallTwo is used to process and mount reolve, reject
  // Pass three arguments, the Promise constructor itself, the resolve callback, and the reject callback
  var res = tryCallTwo(fn, function (value) {
    if (done) return;
    done = true;
    // Resolve method
    resolve(promise, value);
  }, function (reason) {
    if (done) return;
    done = true;
    // Reject method
    reject(promise, reason);
  });
  // Reject the error
  if(! done && res === IS_ERROR) { done =true; reject(promise, LAST_ERROR); }}Copy the code

The doResolve method takes two arguments (the Promise constructor, the Promise instance being this). TryCallTwo here plays an important role by executing the fn constructor, passing in three arguments (the fn constructor, the resolve callback, Reject callback) let’s go back to the tryCallTwo function

// Execute the Promise constructor
function tryCallTwo(fn, a, b) {
    / /...
    fn(a, b);
    / /...
}
Copy the code

So a and B are the Promise’s resolve and reject call fn and pass in a and B and then resolve and Reject call resolve and reject call resolve when the instance calls resolve, Let’s look at the resove method

Resolve method

function resolve(self, newValue) {
  // Prevent the value of resolve from being passed into the instance itself
  if (newValue === self) {
    return reject(
      self,
      new TypeError('A promise cannot be resolved with itself.')); }// If is used to resolve a Promise
  if (
    newValue &&
    (typeof newValue === 'object' || typeof newValue === 'function')) {// Get the Promise's then method
    var then = getThen(newValue);
    if (then === IS_ERROR) {
      return reject(self, LAST_ERROR);
    }
    // If an instance of Promise is passed in
    if (
      then === self.then &&
      newValue instanceof newPromise
    ) {
      self._state = 3;
      // Mount the incoming Promise instance directly to value
      self._value = newValue;
      finale(self);
      return;
    } else if (typeof then === 'function') {  // If passed with the then method
      // Treat then as a constructor and refer this to the object of that THEN
      doResolve(then.bind(newValue), self);
      return;
    }
  }
  self._state = 1;
  self._value = newValue;
  finale(self);
}
Copy the code

The resolve method starts by determining the value passed in to resolve. If resolve is passed in as a Promise instance, Changing the instance state to 3 changes the instance value to the incoming Promise instance. Call the finale method. If resolve is passed as a Promise instance and contains the THEN method, doResolve is called to execute the constructor for that instance

If resolve is passed a normal value instead of a Promise instance, call the finale method by changing the instance’s state to 1 and changing its value to the value passed in resolve

Reject method

function reject(self, newValue) {
  // reject changes to 2
  self._state = 2;
  // Save the error message to _value
  self._value = newValue;
  if (newPromise._onReject) {
    newPromise._onReject(self, newValue);
  }
  finale(self);
}
Copy the code

Reject status changes to 2 when a Promise executes Reject save the reject error message to the _value call to the finale method

Finale method

function finale(self) {
  // Call then only once
  if (self._deferredState === 1) {
    handle(self, self._deferreds);
    self._deferreds = null;
  }
  // Call multiple times then
  if (self._deferredState === 2) {
    // console.log(self._deferreds);
    for (var i = 0; i < self._deferreds.length; i++) {
      handle(self, self._deferreds[i]);
    }
    self._deferreds = null; }}Copy the code

The finaale method acts as a sort of staging post for the Handle method, calling the Handle depending on the situation and as mentioned above _deferredState is used to record the state of the stored instance under the same Promise object, The value of _deferredState is determined by the number of calls to THEN. Why is it only triggered by the same Promise object? Here’s a quick example

const promise2 = new Promise((resolve,reject) = > {
    setTimeout(() = > {
      resolve('Hahaha')},500);
  })
  .then(res= > {})	// First time then
  .then(res= > {})	// The second time then
Copy the code

If (self._deferredState === 2){}, _deferredState will always be 1, If (self._deferredState === 1){} ‘, then the kids will say, “No, I’m under the same Promise, I only instantiated it once.” It’s true that we only instantiate a Promise once, but each call to then returns a Promise that is not the same reference as the instance’s Promise. In other words, self is not the instantiated object. We’ll talk more about how then returns a Promise

promise2.then(res= > {
  console.log(res);
})
promise2.then(res= > {
  console.log(res);
})
Copy the code

If (self._deferredState === 2){} ‘is executed only when then is called

Promise.prototype.then

Promise.prototype.then = function(onFulfilled, onRejected) {
  if (this.constructor ! = =Promise) {
    return safeThen(this, onFulfilled, onRejected);
  }
  // Create a new Promise instance
  var res = new Promise(noop);
  // // new Handler builds an object containing the new Promise instance and the resolve, Reject methods
  handle(this.new Handler(onFulfilled, onRejected, res));
  // A new PROMISE instance is returned after each then processing
  return res;
};
Copy the code

The resolve and reject functions start by deciding whether the instance’s constructor is Promise(to prevent external changes to prototype.constructor). The safeThen function is used to instantiate the external instance’s constructor and return it Create an empty Promise instance for res. What does this code do

handle(this, new Handler(onFulfilled, onRejected, res));
Copy the code

So let’s break it down and see what happens when we instantiate new Handler

The resolve and reject constructor builds a new object each time then
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

So you can get an object that looks something like this by instantiating the Handler function

So handle takes two arguments, one to this and one to an object that looks like this. So, what does Handle do

function handle(self, deferred) {
  // Resolve is passed in an instance of the promise, and this (context) is passed in as an instance of the promise
  while (self._state === 3) {
    self = self._value;
  }
  if (newPromise._onHandle) {
    newPromise._onHandle(self);
  }
  // In the padding state (resolve and reject are not called)
  // Store the new Promise instance
  if (self._state === 0) {
    if (self._deferredState === 0) {
      self._deferredState = 1;
      self._deferreds = deferred;
      return;
    }
    // Then has been called once
    if (self._deferredState === 1) {
      self._deferredState = 2;
      self._deferreds = [self._deferreds, deferred];
      return;
    }
    / / then many times
    self._deferreds.push(deferred);
    return;
  }
  handleResolved(self, deferred);
}
Copy the code

The handle function is used to change the value of _deferredState and save a new instance of it each time it is generated

Finally, return RES returns a new Promise instance each time it calls the THEN method

Chain calls

If you are careful, you may have noticed that at the bottom of the previous Handle method a function called handleResolved is called

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

  After resolve, get the callback for then
  var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
  // If there is no callback for then, call the callback manually
  if (cb === null) {
    if (self._state === 1) {
      resolve(deferred.promise, self._value);
    } else {
      reject(deferred.promise, self._value);
    }
    return;
  }
  // Get the return value of then
  var ret = tryCallOne(cb, self._value);
  if (ret === IS_ERROR) {
    reject(deferred.promise, LAST_ERROR);
  } else{ resolve(deferred.promise, ret); }}Copy the code

In fact, source code also introduced asap this library, I first put the ASAP function annotation out, the reason is too lazy to use NPM, this article is HTML + JS +live-server but we must not ignore ASAP this function!! So far, you don’t seem to have noticed any asynchrony in the source code, you know that promises are implemented asynchronously, SetImmediate mediate is the core method of implementing things asynchronously through the ASAP function. If you’re interested, look at the SOURCE code of ASAP and see how it works. It’s just for better parsing of the source code. Without ASAP, the Promise is meaningless

This makes sense by calling tryCallOne to get the return value of THEN and then calling resolve again, reject if an error is reported or reject is called manually, completing the chained call to Promise

extension

src/es6-extensions.js

define

var TRUE = valuePromise(true);
var FALSE = valuePromise(false);
var NULL = valuePromise(null);
var UNDEFINED = valuePromise(undefined);
var ZERO = valuePromise(0);
var EMPTYSTRING = valuePromise(' ');

function valuePromise(value) {
  var p = new newPromise(newPromise._noop);
  p._state = 1;
  p._value = value;
  return p;
}
Copy the code

Promise.resolve

Promise.resolve = function (value) {
  // Check if value is an instance of Promise
  if (value instanceof Promise) return value;

  // Because 0,'',null, etc., are implicitly converted to false, we make a precise judgment here
  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;


  // The same determination as in the resolve method is to determine if the incoming Promise is a Promise
  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 a new Promise according to the valuePromise method
  return valuePromise(value);
};
Copy the code

A try/catch is used to catch promise. resolve whether val passed contains a then method

Promise.all

Promise.all = function (arr) {
  var args = iterableToArray(arr);

  return new Promise(function (resolve, reject) {
    if (args.length === 0) return resolve([]);
    var remaining = args.length;
    function res(i, val) {
      if (val && (typeof val === 'object' || typeof val === 'function')) {
        // If val is an instance of Promise
        if (val instanceof Promise && val.then === Promise.prototype.then) {
          // _state equals 3 to prove that val is also a Promise instance. Replace val with a new Promise instance
          while (val._state === 3) {
            val = val._value;
          }
          Resolved successfully called, recursively handling resolved values
          if (val._state === 1) return res(i, val._value);
          if (val._state === 2) reject(val._value);
          // Call the then method while in the padding state and manually process the value
          val.then(function (val) {
            res(i, val);
          }, reject);
          return;
        } else {
          // If it is not an instance of Promise and contains the then method
          var then = val.then;
          if (typeof then === 'function') {
            var p = new Promise(then.bind(val));
            p.then(function (val) {
              res(i, val);
            }, reject);
            return;
          }
        }
      }
      args[i] = val;
      // This is a big pity
      if (--remaining === 0) { resolve(args); }}for (var i = 0; i < args.length; i++) { res(i, args[i]); }}); };Copy the code

The first line uses the iterableToArray function, whose main purpose is to convert an array of classes into an array that can be traversed

const p1 = new Promise(() = > {})
const p2 = new Promise(() = > {})
console.log(Array.isArray(Promise.all[p1,p2]))  //false
Copy the code
Array.form is compatible with es6 syntax
var iterableToArray = function (可迭代) {
  if (typeof Array.from === 'function') {
    // ES2015+, iterables exist
    iterableToArray = Array.from;
    return Array.from(iterable);
  }

  // ES5, only arrays and array-likes exist
  iterableToArray = function (x) { return Array.prototype.slice.call(x); };
  return Array.prototype.slice.call(iterable);
}
Copy the code

Use Array. Prototype. Slice. Call (x) to the Array. The from made compatible

Promise.all = function (arr) {
  var args = iterableToArray(arr);

  return new Promise(function (resolve, reject) {
    if (args.length === 0) return resolve([]);
    var remaining = args.length;
    function res(i, val) {... }for (var i = 0; i < args.length; i++) { res(i, args[i]); }}); };Copy the code

So instead of looking at the RES method, let’s look at what we’re going to do with the parameters that are passed in and we’re going to loop through the RES method and we’re going to process each item that’s passed in, so the first parameter is the index, the second parameter is each item in the array and let’s look at the RES method, okay

function res(i, val) {
      if (val && (typeof val === 'object' || typeof val === 'function')) {
        // If val is an instance of Promise
        if (val instanceof Promise && val.then === Promise.prototype.then) {
          // _state equals 3 to prove that val is also a Promise instance. Replace val with a new Promise instance
          while (val._state === 3) {
            val = val._value;
          }
          Resolved successfully called, recursively handling resolved values
          if (val._state === 1) return res(i, val._value);
          if (val._state === 2) reject(val._value);
          // Call the then method while in the padding state and manually process the value
          val.then(function (val) {
            res(i, val);
          }, reject);
          return;
        } else {
          // If it is not an instance of Promise and contains the then method
          var then = val.then;
          if (typeof then === 'function') {
            var p = new Promise(then.bind(val));
            p.then(function (val) {
              res(i, val);
            }, reject);
            return;
          }
        }
      }
      args[i] = val;
      // This is a big pity
      if (--remaining === 0) { resolve(args); }}Copy the code

If val (each item of args) is passed in that is not an object or function, replace it with args[I]

args[i] = val;
Copy the code

If a Promise is passed in

if (val instanceof Promise && val.then === Promise.prototype.then) {
      ...
}
Copy the code

If the val passed in is not a Promise and contains the then method

else {
  // If it is not an instance of Promise and contains the then method
  var then = val.then;
  if (typeof then === 'function') {
    var p = new newPromise(then.bind(val));
    p.then(function (val) {
      res(i, val);
    }, reject);
    return; }}Copy the code

The emphasis is on this paragraph

// _state equals 3 to prove that val is also a Promise instance. Replace val with a new Promise instance
  while (val._state === 3) {
    val = val._value;
  }
  Resolved successfully called, recursively handling resolved values
  if (val._state === 1) return res(i, val._value);
  if (val._state === 2) reject(val._value);
  // Call the then method while in the padding state and manually process the value
  val.then(function (val) {
    res(i, val);
  }, reject);
  return;
Copy the code

This is a big pity. Only when the state of Promise is Fulfilled, the value of the instance will be correctly processed, otherwise the return will be implemented. Therefore, as long as one Promise fails to fulfill, the resolve(args) will not be implemented.

// The condition cannot be met
if (--remaining === 0) {
    resolve(args);
}
Copy the code

When all the result values are returned (args[I] = val) == args[I] = val.value, call the resolve method and pass in the result array args

Look at an example

const promise2 = new Promise((resolve,reject) = > {
    setTimeout(() = > {
      resolve('Hahaha')},700);
})
const promise3 = new Promise((resolve,reject) = > {
    setTimeout(() = > {
      resolve('Hahaha 2')},600);
})
  
newPromise.all([promise2,promise3])
.then(res= > {
    console.log(res);  //[' hahaha ',' hahaha 2']
})
Copy the code

Res (I, args[I]) is used to output the array res(I, args[I]) in the same order as promise.all ([]).

Promise.race

Promise.race = function (values) {
  return new Promise(function (resolve, reject) {
    iterableToArray(values).forEach(function(value){
      Promise.resolve(value).then(resolve, reject);
    });
  });
};
Copy the code

Promise.race passes in an array, Resolve (value).then(resolve, The state will not change after the Promise executes resolve once. Therefore, race will pass in multiple promises. The Promise whose state will become a big pity first will be returned

const promise2 = new Promise((resolve,reject) = > {
    setTimeout(() = > {
      resolve('Hahaha')},700);
})
const promise3 = new Promise((resolve,reject) = > {
    setTimeout(() = > {
      resolve('Hahaha 2')},600);
})

Promise.race([promise2,promise3])
.then(res= > {
    console.log(res);  // Hahaha 2
})
Copy the code