In this week’s coding process, NodeJs was used to process pictures. Because all the interfaces for image processing were promises, my program was full of nested returns of promises, and another Promise was returned in the result of THEN. Another Promise in a series of then’s would return a new Promise, which confused me for a while.

After clear, can not help but wonder why Promise can be so magical, so to read its source code, only to find some used to function behind the wonderful design.

Promise to use

ES6 provides a Promise constructor. We create an instance of a Promise. The Promise constructor takes a function as an argument. Resolve changes the state of the Promise from unsuccessful to successful, passing the result of the asynchronous operation as a parameter; Similarly, reject changes the state from reject to fail, is called when an asynchronous operation fails, and any errors reported by the asynchronous operation are passed as arguments. Once the instance is created, you can use the THEN method to specify successful or failed callback functions, which is more elegant and readable than the layer upon layer writing of f1(F2 (F3))

When we create a new Promise, we need to pass in a function as a parameter. The function has two parameters, resolve and reject. The asynchronous operation is performed inside the function body, and then different functions are called according to whether the result of the operation is the desired result. Call resolve and take the result you want to return as an argument, or reject and take the error message as an argument if you don’t get the right one, so the outside world can get the error message. Here’s a simple example:

const promise = new Promise((resolve, reject) = > {
    setTimeout(() = > {
        const num = Math.random();
        num > . 5 ? resolve(`success:${num}`) : reject(`fail:${num}`);
    }, 1000);
});

promise.then((resolveVal) = > {
    console.log(val);
}).catch(rejectVal= > {
    console.log(rejectVal)
})
Copy the code

This example creates a Promise that, after a second, generates a random number that returns resolve if greater than 0.5, reject if less than 0.5, and reject if less than 0.5.

When resolve is executed, the Promise state will change to Resolved and the function passed in then will be executed. In effect, the function passed in then will be executed as resolve. Similarly, when reject is executed, the state of the Promise changes to Rejected. In this case, the function passed in the catch is executed as reject.

In summary, then and catch load promises with two specific functions that resolve and Reject execute.

Then the rules of

  • The next input to the then method requires the last output

  • If another promise is returned after a promise is executed, the execution result of the promise is passed to the next THEN. That is, if you return a PromiseB in the then of the PromiseA, the result of the PromiseB will be used as the input of the next THEN of the PromiseA.

    const promiseA = new Promise((resolve, reject) = >{... });const promiseB = new Promise((resolve, reject) = > {
        
    })
    promiseA.then((resolveA) = > {
        return promiseB;
    }).then((resolveB) = >{})Copy the code

    In this case, the second then argument is the return result of the Promise.

  • If instead of a Promise object, an ordinary value is returned in the THEN, the result is taken as the result of the success of the next THEN

  • If the current THEN fails, the next THEN fails

  • If undefined is returned, success or failure will be followed by the next success

  • If the method is not written in then, the value is passed through to the next THEN

  • Return Val in the then function is the same as Promise resolve (val)

For example:

//example1
promise1 = new Promise((resolve) = > {
    setTimeout(() = > {
        resolve('promise1');
    }, 1000)})Copy the code

When the argument is a non-PROMISE, the state of the promise immediately changes to resolve after 1 second, and the events in then are executed.

//example2
promise1 = new Promise((resolve) = > {
    setTimeout(() = > {
        promise2 = new Promise((resolve, reject) = > {
            resolve('promise2');
        })
        resolve(promise2);
    }, 1000)})Copy the code

When the parameter is another promise, the state of promise1 is determined by promisE2. When the state of promisE1 changes, the state of promisE1 changes accordingly and the state remains the same.

//example3
promise1 = new Promise((resolve) = > {
    resolve('promise1');
})
promise2 = promise1.then((data) = > {
    return 'promise2';
})
Copy the code

When a callback returns a non-promise, as in example1, the current promise2 state changes to resolve. Resolve (‘ non-promise ‘)

//example4
promise1 = new Promise((resolve) = > {
    resolve('promise1');
})
promise2 = promise1.then((data) = > {
    promise3 = new Promise((resolve, reject) = > {
        resolve('promise3');
    })
    return promise3;
})
Copy the code

When a callback returns promise3, as in example2, the current state of promise2 depends on primise3. Resolve (promise3)

//example5
promise1 = new Promise((resolve) = > {
    resolve('promise1');
})
promise2 = promise1.then((data) = > {
    console.log( iamnotundefined );
})
Copy the code

When a callback fails, the promise state changes to reject, reject, and no catch is received.

The rules of the catch

  • Reject calls catch
  • A catch is executed when any previous error occurs that has not been handled

Source code analysis

Promise source code address

The following comment illustrates possible values for the Promise state:

0 – Waiting 1 – condition satisfied (value _value) 2 – Condition rejected (value _value) 3 – State and value of another Promise adopted

Once the state value is not zero, the Promise cannot be changed.

Before formally declaring promises, several utility functions were defined to reduce the appearance of try catches in the code,

Tool function

function noop() {} // Empty callback function for then

// States:
//
// 0 - pending
// 1 - fulfilled with _value
// 2 - rejected with _value
// 3 - adopted the state of another promise, _value
//
// once the state is no longer pending (0) it is immutable

// All `_` prefixed properties will be reduced to `_{random number}`
// at build time to obfuscate them and discourage their use.
// We don't use symbols or Object.defineProperty to fully hide them
// because the performance isn't good enough.


// to avoid using try/catch inside critical functions, we
// extract them to here.
var LAST_ERROR = null;
var IS_ERROR = {};
function getThen(obj) {
  try {
    return obj.then;
  } catch (ex) {
    LAST_ERROR = ex;
    returnIS_ERROR; }}function tryCallOne(fn, a) {
  try {
    return fn(a);
  } catch (ex) {
    LAST_ERROR = ex;
    returnIS_ERROR; }}function tryCallTwo(fn, a, b) {
  try {
    fn(a, b);
  } catch (ex) {
    LAST_ERROR = ex;
    returnIS_ERROR; }}Copy the code

The statement

module.exports = Promise;
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;// This state is used to indicate a future state. It is only useful if the current Promise state depends on another Promise, i.e. resolve a Promise
  this._state = 0;// The current state of the Promise
  this._value = null;// The resolve value of the current Promise
  this._deferreds = null;// Array of callback functions to execute when _deferredState becomes successful, that is, greater than 2
  if (fn === noop) return;
  doResolve(fn, this);
}
Copy the code

The function Promise takes a function as its argument and must be created with new.

Initialize _deferredState and _deferredState to 0, _value and _deferreds to null, and return if the function passed in is an empty function. Normally, enter doResolve to start the process.

doResolve

function doResolve(fn, promise) {
  var done = false;
    // Note that fn is the same as we passed in new Promise (resolve, Reject) => {if success resolve else reject},tryCallTwo will pass the second argument to resolve and the third argument to reject, So when we call resolve in the declared Promise, we actually call the second argument to trayCallTwo.
  var res = tryCallTwo(fn, function (value) {
    if (done) return;// Prevent running twice
    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

In this case, two functions (resolve and reject written externally) are passed in as parameters. After the call is complete, check whether there is an error in the case of incomplete. If it is direct reject. For the resolve and reject functions passed in, wait for the result, and if not complete, continue the process through the resolve and Reject functions defined in this file.

Note that the second and third arguments to tryCallTwo are functions that were passed in when we created a new Promise. Resolve and Reject are the internal methods defined in the Promise.

resolve & reject

function resolve(self, newValue) {
 // The result of a Promise cannot be its own (because according to the then principle we mentioned in the beginning, if you return a new Promise, the current state of the Promise depends on the new Promise, if you depend on yourself, So it's always looping dependencies and pending.)
    
// newValue is the resolve parameter of the Promise instance. It can be a string, an array, or another Promise
  if (newValue === self) {
    return reject(
      self,
      new TypeError('A promise cannot be resolved with itself.')); }// When the new value (resolve) exists and the type is an object or function
  // Typeof Promised instance === 'object'
  // The condition for if to succeed is to resolve an object, function, or Promise instance
  if (
    newValue &&
    (typeof newValue === 'object' || typeof newValue === 'function')) {var then = getThen(newValue);  // let then = newValue.then
    if (then === IS_ERROR) { IS_ERROR is declared in the tool above, which is an empty object and is IS_ERROR only if an exception occurs to return newValue.then
      return reject(self, LAST_ERROR);
    }
    if (
      then === self.then &&
      newValue instanceof Promise // If resvole is a Promise, and the Promise's THEN is the same as the current Promise's THEN (this THEN is usually the same, and is defined in the prototype of the Promise), the current Promise's result is used as the final result.
    ) {
      self._state = 3; // State 3 indicates that another Promise is adopted as the result
      self._value = newValue;
      finale(self); // The result of adopting this Promise
      return;
    } else if (typeof then === 'function') {Resolve an object with a then method (or a Promise, which is less common than the current Promise's THEN)
      doResolve(then.bind(newValue), self); The first argument to doResolve is the function we passed in as a new Promise, and the second argument is a pointer to the current Promise instance. The "this" inside of it refers to this newValue right here, where self doesn't change
      return; }}// Resolve is resolved with a normal value, such as a number, a string, or a complete flag
  self._state = 1; // Status 1 Indicates that the status is successful
  self._value = newValue;
  finale(self);
}

function reject(self, newValue) {
  // Set the reject status and reason
  self._state = 2;
  self._value = newValue;
  if (Promise._onReject) {
    Promise._onReject(self, newValue); // Process callback notification
  }
  finale(self); / / end
}
Copy the code
_deferredState is 1 to indicate that _deferreds is a Handler. If the status of _deferredState is 1, then _deferredState is a Handler. When _deferredState is 2, _deferredState is an array of handlers. When all of this is done, set _deferredState to empty
// Call begins by executing all handlers once
// Each Hander is passed in through the THEN method, which has two parameters, ondepressing, onRejected, which is a function that should be implemented in resolve and reject
// Initiate a call if _state is 3, which means attaching the Handler created by the current Promise through THEN to the _deferReds of that dependent Promise instance
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

What are the uses of _deferredState and _deferredReds that we haven’t touched on before? Before we answer these questions, let’s recall that in the case of promises, when a Promise is created and returned, then operations are used to fetch results (including catch).

then

// Then parameters are two functions, onFulfilled is the callback function called resolve and onRejected is the callback function called reject
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

The usage of safeThen is basically the same as that of THEN. Both create an asynchronous empty callback RES, and then use onFulfilled, onRejected and res to create a Handler. The kernel lies in the handle function:

function handle(self, deferred) {
  //self._state === 3 Indicates that the state of the Promise instance referred to by self depends on another Promise instance, self._value
  That is, through this loop, self will eventually point to a Promise instance whose state is dependent on its own
  // If self is P1, it depends on the status of P2. When P1 calls handle and P2 has not resolved yet, the Handler created by P1 through then is attached to p2's _deferreds.
  while (self._state === 3) {
    self = self._value;
  }
  if (Promise._onHandle) { // for injection - not in main loop
    Promise._onHandle(self);
  }
  // If the state depends only on its Promise instance and no result is forthcoming, save the incoming callback
  // _deferredState is 0 to indicate that no callback has been saved, so assign the callback function to _deferredreds, which is only a callback function at this point
  // if _deferredState is 1, the reds callback function is saved, and the old _deferredcallback function saved in _deferredState is reassigned to _Deferreds as an array. _deferredState is just an array of callbacks
  // _deferredState is 2 to indicate that _deferredReds is already an array of callback functions
  // handleResolved is executed only when the state of handle self is no longer zero
  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

The argument to this function is deferred, so deferred is Handler. Specifically, what you need to do after fulfilling a promise. So how does that work? First determine if the current state is dependent on another promise, if it is, wait through the while and then the onHandle is just a progress callback provided to the outside world, ignoring the fact that when the state is zero, it’s setting up future processing,

If the future state is not set (0), then set the callback (deferred) for a separate callback if the future state is set (1), then set the callback (deferredEnter the callback array if other states (2+), then directly into the callback array. The state of0The processing of the case is returned here. Because at this point, it's the promise that synchronizes the incoming THEN, setting up the function for future processing.Copy the code

When the state is non-zero, handleResolved is entered, and this should be where the post-processing ends. Wait, this is just starting from then into Handle. The promise has not yet completed, and the call to handle it must be somewhere else. Call to initiate if you want to call handle. Call to initiate if you want to call Handle. Call to initiate if you want to call Handle.

State 1, state 3, waiting for other promise results - this will go into state 2, state 1, complete, state 3, State 2, RejectCopy the code

As you can see, you only enter finale when a promise ends or relies on another promise.

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

Call Handle one by one, and call handleResolved directly into the call as follows:

function handleResolved(self, deferred) {
  asap(function() {
    If self._state is 1, then executes the first argument, which is the successful callback, or reject
    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

This is a relatively simple task, which is called through asynchronous ASAP. If there is no onFulfilled(onFulfilled failure), then resolve(reject) will be called directly. If there is, onFulfilled(onFulfilled failure) will be called first. Resolve (reject) is called based on the result.

And so on. Don’t resolve and reject appear in the above process? Note that the resolve and Rejected promise is an empty promise created in the then case, meaning that nothing will be performed (and this goes directly to the finale without the handle). So what really affects the process here is the callback of deferred. Ondepressing or deferred. OnRejected. After the callback is implemented, the promise implementation process will be completed.

In summary, the implementation process of a promise looks like this

  • Create a Promise
  • Set the function that needs to be executed, that is, new Promise is the function passed in
  • Resolve (reject), reject (reject), resolve (reject), and resolve (reject). If resolve (reject) is used, the Handler created by the then will be mounted on the Promise2. Wait until Promise2 Resolve is resolved.
  • Start executing the function
  • Select the callback based on the result of execution

SafeThen, the purpose of safeThen is to continue the safe execution of then when the environment this is no longer a Promise.

Reference article:

Juejin. Cn/post / 684490…

Juejin. Cn/post / 684490…

www.jianshu.com/p/b63ec30ee…