Promise is a solution to asynchronous programming that is more rational and powerful than traditional solutions (callbacks and events). Promises are now widely used in front-end applications. This article through the implementation of Promise/A+ specification Promise, to deepen the impression.

The constructor

When we use a Promise, we usually construct it using the new operator and pass in the resolver function, which accepts the resolve or reject callbacks. When we determine the result, we need to call resolve or reject.

const p1 = new Promise((resolve, reject) = > {
  setTimeout((a)= > resolve('success'), 1000)})// The console prints success after 1s
p1.then(res= > console.log(res))
Copy the code

So our Promise also needs to be a constructor and execute the resolver function passed in by the user, passing in the defined callback function. Here’s the code:

Note: In the implementation of this code, the beginning of the underscore represents the private property, private method.

// Define three state constants for Promise
const PENDING = 'PENDING';
const FULFILLED = 'FULFILLED';
const REJECTED = 'REJECTED';

function Promise(resolver) {
  // You must pass a function
  if (typeofresolver ! = ='function') {
    throw new TypeError('Promise resolver ' + resolver + ' is not a function');
  }
  
  // The result of resolve or reject
  this._result = undefined;
  / / state
  this._status = PENDING;

  try {
    / / execution
    resolver(this._resolve.bind(this), this._reject.bind(this));
  } catch (error) {
    // Catch an error
    this._reject(error); }}// Private method, passed to resolver's success and failure callbacks
Promise.prototype._resolve = function() {}
Promise.prototype._reject = function() {}
Copy the code

We implement the _resolve and _reject private methods. The logic is simple. We only need to change the Promise state and the value of success or the reason for failure. Note, however, that once the state of a Promise changes, it does not change again. This result is available at any time, so we only execute if the state is PENDING.

Promise.prototype._resolve = function(value) {
  // setTimeout for asynchronous execution
  setTimeout((a)= > {
    if (this._status ! == PENDING)return;
    this._status = FULFILLED;
    this._result = value;
  });
}

Promise.prototype._reject = function (reason) {
  // setTimeout for asynchronous execution
  setTimeout((a)= > {
    if (this._status ! == PENDING)return;
    this._status = REJECTED;
    this._result = reason;
  });
};
Copy the code

Promise.prototype.then

The core of Promise is the THEN method, Promise/A+ specification is mostly for the THEN method, after the implementation of the THEN method, we then to implement other APIS will be much more convenient.

The specification for a Promise’s THEN method is as follows

  1. Accept two arguments, onFulfilled and onRejected. If the callback is not a function, it must be ignored. Through pass is supported

  2. The THEN method can be called multiple times with the same Promise

    • When a Promise executes successfully, all onFulfilled needs to be called back in the order in which it was registered
    • When a Promise is rejected, all onRejected are called back in the order in which they were registered
  3. The THEN method must return a Promise object

    • If onFulfilled or onRejected returns a value x, the following Promise resolution is run
    • If onFulfilled or onRejected throws an exception e, promise2 must refuse to execute and return rejected e

Let’s implement the above points respectively

For the first point, the console can execute the following code

const p1 = new Promise((resolve, reject) = > {
  setTimeout((a)= > resolve('success'), 1000)})// The console prints success after 1s
p1.then(1).then(res= > console.log(res))
Copy the code

The second THEN method can still accept the value of resolve successfully, so when the THEN method is not passed in as a function, we need to make it a function that supports transparent-passing.

Promise.prototype.then = function (onFulfilled, onRejected) {
  // Make sure it is a function, not a function
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) = > v;
  onRejected = typeof onRejected === 'function' ? onRejected : (v) = > { throw v };
}
Copy the code

Implementing the first point is as simple as passing the result/error -> return/throw.

Let’s look at the second point. In order to be able to call multiple times and execute in sequence, we need to change the code we wrote before. We need to add two callback queues, one for success and one for failure. You can actually store it in a queue, but I’m going to store it separately.

Function Promise(resolver) {// ignore irrelevant code... // Callback queue for resolve+ this._resolveCbs = [];// Reject callback queue+ this._rejectCbs = [];} promise.prototype. _resolve = function(value) {setTimeout(() => {// ignore the code...+ this._resolveCbs.forEach((callback) => callback(value));}); } promise.prototype._reject = function(reason) {setTimeout(() => {// Ignore extranet code...+ this._rejectCbs.forEach((callback) => callback(reason));
  });
}
Copy the code

In the THEN method, if the state is PENDING, we need to insert the onFulfilled(success callback) and onRejected(failure callback) into the corresponding queue. Otherwise, we just execute directly. This is the core logic that we need to implement point 3.

According to the third point, we always need to execute onFulfilled or onRejected, and then pass in the Promise resolution process. In addition, we need to capture this process, reject directly. The specific core code is as follows

let promise = undefined;
return (promise = new Promise((resolve, reject) = > {
  setTimeout((a)= > {
    try {
      var x = onFulfilled(this._result);  Var x = onRejected(this._result); var x = onRejected(this._result);
      // The next section describes the resolution process
      resolvePromise(promise, x, resolve, reject);
    } catch (e) {
      returnreject(e); }}); }));Copy the code

This is very depressing. This is very depressing. This is very depressing.

Promise.prototype.then = function (onFulfilled, onRejected) {
  // Make sure it is a function, not a function
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (v) = > v;
  onRejected =
    typeof onRejected === 'function'
      ? onRejected
      : (v) = > {
          throw v;
        };

  let promise = undefined;

  / / has been resolve
  if (this._status === FULFILLED) {
    return (promise = new Promise((resolve, reject) = > {
      setTimeout((a)= > {
        try {
          var x = onFulfilled(this._result);
          resolvePromise(promise, x, resolve, reject);
        } catch (e) {
          returnreject(e); }}); })); }/ / has been reject
  if (this._status === REJECTED) {
    return (promise = new Promise((resolve, reject) = > {
      setTimeout((a)= > {
        try {
          var x = onRejected(this._result);
          resolvePromise(promise, x, resolve, reject);
        } catch (e) {
          returnreject(e); }}); })); }// pending is directly placed in the callback queue. SetTimeout is not required for queue summary, because the execution is already in setTimeout
  if (this._status === PENDING) {
    return (promise = new Promise((resolve, reject) = > {
      this._resolveCbs.push((value) = > {
        try {
          var x = onFulfilled(value);
          resolvePromise(promise, x, resolve, reject);
        } catch (e) {
          returnreject(e); }});this._rejectCbs.push((reason) = > {
        try {
          var x = onRejected(reason);
          resolvePromise(promise, x, resolve, reject);
        } catch (e) {
          returnreject(e); }}); })); }};Copy the code

The above code looks like a lot of complex code, but according to the specification, it’s actually quite simple and has a lot of duplicated code. We still have a resolvePromise function to complete, so I hope the reader can read the logic of the Promise solution for himself. Click the link to check it out. Because the implementation of the function is completely written according to the logic of the specification, there is no skill to speak of.

Here are some simple summaries:

  1. This is very depressing. In order to live a promise with other promises, we can’t just judge whether the onRejected function returns a promise. We just need to make sure that the return value exists and then the method will try to treat it as a promise.

  2. Resolve (x) is handled directly if there is no THEN attribute or if the THEN attribute is not a function

  3. If then exists and is a function, the error needs to be captured as reject(x) while the PROMISE is processed

  4. If all callbacks passed in are called, or the same argument is called multiple times, the first call takes precedence and the rest are ignored (only one call, flag bit required)

Using these four points, we can write the code for the resolvePromise function

function resolvePromise(promise, x, resolve, reject) {
  // If the promise and x point to the same object, refuse to execute the promise on TypeError grounds
  if (x === promise) {
    return reject(new TypeError('Chaining cycle detected for promise! '));
  }
  // The flag bit for "take the first call first and ignore the rest"
  let invoked = false;
  // Try to assign x.then to then
  let then = undefined;
  // x is an object or function
  if((x ! = =null && typeof x === 'object') | |typeof x === 'function') {
    try {
      then = x.then;
      if (typeof then === 'function') {
        If then is a function, call this with x as the scope of the function
        then.call(
          x,
          (y) => {
            if (invoked) return;
            invoked = true;
            return resolvePromise(promise, y, resolve, reject);
          },
          (r) => {
            if (invoked) return;
            invoked = true;
            returnreject(r); }); }else {
        // If then is not a function, execute the promise with x as the argument
        returnresolve(x); }}catch (e) {
      // If an error e is thrown with the value of x. hen, the promise is rejected with e as the data
      if (invoked) return;
      invoked = true;
      returnreject(e); }}else {
    // If x is not an object or function, execute the promise with x as the argument
    returnresolve(x); }}Copy the code

Promise.prototype.catch

The.catch() callback function is equivalent to.then(null, onRejected). We implement the THEN method so the catch method is fairly simple

Promise.prototype.catch = function (onRejected) {
  return this.then(null, onRejected);
};
Copy the code

Promise.prototype.finally

The.finally() method is used to specify an action that will be performed regardless of the final state of the Promise object.

promise
.then(result= >{...}). The catch (error= >{...}). Finally,(a)= > {···});
Copy the code

In the above code, the finally callback is executed after the then or catch callback, regardless of the final state of the promise.

We can then execute both the success and failure callbacks passed in through the then method

Promise.prototype.finally = function (callback) {
  return this.then(
    (value) = > Promise.resolve(callback()).then((a)= > value),
    (reason) =>
      Promise.resolve(callback()).then((a)= > {
        throwreason; })); };Copy the code

Notice that we want.THEN to pass the result through, because finally can continue to call the THEN method.

// The last "then" should take an argument of 2
Promise.resolve(2).finally((a)= > { }).then(res= > console.log(res))
Copy the code

Promise.resolve

Sometimes you need to convert an existing object to a Promise object, and the promise.resolve () method does that.

We just need to take the success logic from the THEN method and use it. (Where x is not the value of onFulfilled, it is directly the parameter passed in.)

Promise.resolve = function (value) {
  let promise;
  return (promise = new Promise((resolve, reject) = > {
    resolvePromise(promise, value, resolve, reject);
  }));
};
Copy the code

Promise.reject

The promise.reject (reason) method also returns a new Promise instance with the state Rejected.

Promise.reject = function (reason) {
  return new Promise((_, reject) = > reject(reason));
};
Copy the code

Promise.all

The /** * promise.all () method is used to wrap multiple Promise instances into a new one. This is fulfilled only if all the instances are fulfilled. The new instance parameter is fulfilled only if one of the instances is rejected. The return value of the first rejected instance is passed to the callback function for the new instance. * /
Promise.all = function (promises) {
  return new Promise(function (resolve, reject) {
    let resolvedCount = 0;
    let promiseCount = promises.length;
    let resolvedValues = new Array(promiseCount);
    for (let i = 0; i < promiseCount; i++) {
      Promise.resolve(promises[i]).then(
        (value) = > {
          resolvedCount++;
          resolvedValues[i] = value;
          // The same number indicates that promise instances are successful
          if (resolvedCount == promiseCount) {
            return resolve(resolvedValues);
          }
        },
        (reason) => {
          Reject (reject); reject (reject)
          returnreject(reason); }); }}); };Copy the code

Promise.race

The /** * promise.race () method is used to wrap multiple Promise instances into a new Promise instance. * As soon as one of the states changes, the new instance follows the change and the arguments are passed to the callback function for the new instance. * /
Promise.race = function (promises) {
  return new Promise((resolve, reject) = > {
    for (var i = 0; i < promises.length; i++) {
      // He who is quick decides!
      Promise.resolve(promises[i]).then(
        (value) = > {
          return resolve(value);
        },
        (reason) => {
          returnreject(reason); }); }}); };Copy the code

conclusion

The implementation difficulty of Promise is centered on the THEN method, based on which all other methods are implemented. Implementing a Promise is also a common topic in written tests. I hope this article can help you.

Complete source portal

reference

  • Promise/A+ Specification Chinese version

  • Nguyen other ES6

  • Handwritten Promise the simplest version of 20 lines, the realization of asynchronous chain call

  • Github.com/xieranmaya/…

  • Promise implementation principle (with source code) excavation