preface

I believe that Promise is often used, or with asynchronous solutions such as Generator, ASNYC /await, and the Promise principle is everywhere on the Internet. Always want to spare time to also write a Promise to achieve, but the usual work is also busy, just New Year’s day put 3 days holiday, rest 2 and a half days, take out half a day time to see a Promise.

How to Use Promises

new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(1);
  }, 1000)
}).then((data) => {
  console.log(data);
  return new Promise((res) => {
    setTimeout(() => {
      res(2);
    },1000)
  })

}).then((res) => {
  console.log(res);
})

Copy the code

The term

  • PromiseIs one that contains compatibilitypromisespecificationthenMethod object or function.
  • thenableIs one that containsthenMethod object or function.
  • valueIs anyJavascriptValue. (includingundefined.thenable.Promise, etc.).
  • exceptionIs made up ofthrowThe value thrown out of the expression.
  • reasonIs a descriptionPromiseThe value of the reason for rejection.

Due to thePromise/A+The specification does not covercatch,race,allAnd so on method implementation, so here also won’t go to explain in detail.

requirements

A Promise must be in one of these states: pending, fulfilled or rejected. If pending, promise:

This can be fulfilled or rejected. This is very depressing. This is very depressing.

  • You can’t transition to any other state.
  • There must be a value and this value cannot be changed.

If the state is rejected, the promise can:

  • You can’t transition to any other state.
  • There must be a reason and the value cannot be changed.
function MyPromise(callback) {

  let that = this;
  // Define the initial state
  / / Promise
  that.status = 'pending';
  //value
  that.value = 'undefined';
  // Reason is a value that describes why a Promise was rejected.
  that.reason = 'undefined';

  / / define the resolve
  function resolve(value) {
    // When status is pending, define the Javascript value and define its state as fulfilled
    if(that.status === 'pending') {
      that.value = value;
      that.status = 'resolved'; }}/ / define reject
  function reject(reason) {
    // If status is pending, define the reason value and the status is rejected
    if(that.status === 'pending') {
      that.reason = reason;
      that.status = 'rejected'; }}// Catch whether callback reports an error
  try {
    callback(resolve, reject);
  } catch(error) { reject(error); }}Copy the code

A Promise object has a THEN method that registers a callback after the Promise state is determined. The THEN method takes two arguments: promise.then (onFulfilled,onRejected). Let’s write the then function on the prototype.

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  let that = this;

  if(that.status === 'resolved') {
    onFulfilled(that.value);
  }

  if(that.status === 'rejected') { onRejected(that.reason); }}Copy the code

The above code only implements the most basic logic of Promise. If you call THEN directly, it can be executed, but it does not support async. The most important feature of Promise is to solve the problem of asynchronous callback hell. So let’s transform it.

function MyPromise(callback) {

  let that = this;
  // Define the initial state
  / / Promise
  that.status = 'pending';
  //value
  that.value = 'undefined';
  // Reason is a value that describes why a Promise was rejected.
  that.reason = 'undefined';
  // The array used to solve the asynchronous problem
  that.onFullfilledArray = [];
  that.onRejectedArray = [];

  / / define the resolve
  function resolve(value) {
    // When status is pending, define the Javascript value and define its state as fulfilled
    if(that.status === 'pending') {
      that.value = value;
      that.status = 'resolved';
      that.onFullfilledArray.forEach((func) = >{ func(that.value); }); }}/ / define reject
  function reject(reason) {
    // If status is pending, define the reason value and the status is rejected
    if(that.status === 'pending') {
      that.reason = reason;
      that.status = 'rejected';
      that.onRejectedArray.forEach((func) = >{ func(that.reason); }); }}// Catch whether callback reports an error
  try {
    callback(resolve, reject);
  } catch(error) { reject(error); }}Copy the code

Modification of the then function

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  let that = this;
  This is a pity pity. This is a pity pity. This is a pity pity.
  // We have two arrays of on☆ darray
  if(that.status === 'pending') {
    that.onFullfilledArray.push((value) = > {
      onFulfilled(value);
    });

    that.onRejectedArray.push((reason) = > {
      onRejected(reason);
    });
  }

  if(that.status === 'resolved') {
    onFulfilled(that.value);
  }

  if(that.status === 'rejected') { onRejected(that.reason); }}Copy the code

Since the Promise/A+ specification states that A Promise must be in either of the following states: This is very depressing. This is very depressing, which is very depressing. This is very depressing, which is very depressing. Therefore, when we initialize the Promise, we define two arrays onFullfilledArray and onRejectedArray to hold the two callback functions onFulfilled and onRejected. At the same time, we will determine whether status is pending in the THEN function, and then pass onFulfilled and onRejected into the corresponding array respectively. When the user calls resolve or Reject, it changes the state, traverses the group, and executes onFulfilled or onRejected, thus making the call asynchronously.

A chained call to the then function

In the Promise/A+ specification:

For a promise, its THEN method can be called multiple times

  • whenpromise fulfilledAfter allonFulfilledMust be executed in the order in which they were registered.
  • whenpromise rejectedAfter allOnRejectedMust be executed in the order in which they were registered.

Then must return a promise

  • ifonFulfilledonRejectedReturns the valuex, the implementationPromiseAnalytical process[[Resolve]](promise2, x).
  • ifonFulfilledonRejectedAn exception was throwne,promise2Shall be based oneforreasonBe rejected.
  • ifonFulfilledIt’s not a function andpromise1alreadyfulfilled,promise2Have to bepromise1The value of thefulfilled.
  • ifOnRejectIt’s not a function andpromise1alreadyrejected,promise2Must be the samereasonBe rejected.
MyPromise.prototype.then = function(onFulfilled, onRejected) {
  let that = this;
  let promise2;

  // If the then argument is not function, then we need to ignore it
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(f) {};
  onRejected = typeof onRejected === 'function' ? onRejected : function(r) {};
  This is a pity pity. This is a pity pity. This is a pity pity.
  // We have two arrays of on☆ darray
  if(that.status === 'pending') {

    return promise2 = new Promise(function(resolve, reject) {
      that.onFullfilledArray.push((value) = > {
      try {
        let x = onFulfilled(that.value);
        This is a pity pity. If it is, pass the resolve and reject in MyPromise to then.
        // Return a Promise object and take its result directly as the result of promise2
        if(x instanceof MyPromise) {
          x.then(resolve, reject);
        }
        // If not, use its return value as the result of promise2
        resolve(x);
      } catch(error) { reject(error); }}); that.onRejectedArray.push((value) = > {
          try {
            let x = onRejected(that.value);
            // Decide if onPromise.reject (); if it is, reject ();
            // Return a Promise object and take its result directly as the result of promise2
            if(x instanceof MyPromise) {
              x.then(resolve, reject);
            }
            // If not, use its return value as the result of promise2
            resolve(x);
          } catch(error) { reject(error); }}); })}if(that.status === 'fulfilled') {
    return promise2 = new MyPromise(function(resolve, reject) {
      try {
        let x = onFulfilled(that.value);
        This is a pity pity. If it is, pass the resolve and reject in MyPromise to then.
        // Return a Promise object and take its result directly as the result of promise2
        if(x instanceof MyPromise) {
          x.then(resolve, reject);
        }
        // If not, use its return value as the result of promise2
        resolve(x);
      } catch(error) { reject(error); }})}if(that.status === 'rejected') {
    return new MyPromise(function(resolve, reject) {
      try {
        let x = onRejected(that.value);
        // Decide if onPromise.reject (); if it is, reject ();
        // Return a Promise object and take its result directly as the result of promise2
        if(x instanceof MyPromise) {
          x.then(resolve, reject);
        }
        // If not, use its return value as the result of promise2
        resolve(x);
      } catch(error) { reject(error); }}}})Copy the code

When calling then, it determines whether onFulfilled and onRejected are functions. If not, it returns an anonymous function and must return the values of the parameters to solve the problem of breaking through the Promise value in the chain-style call. Such as:

new MyPromise(resolve= >resolve(8))
  .then()
  .then()
  .then(function foo(value) {
    alert(value)
  })
Copy the code

So let’s change this piece to look like this:

  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(f) { return f};
  onRejected = typeof onRejected === 'function' ? onRejected : function(r) {throw r};
Copy the code

However, each judgment requires rewriting the relationship between x and MyPromise, so we need to abstract this piece of code, which is called resolvePromise in the Promise/A+ specification.

function resolvePromise(promise, x, resolve, reject) {
  let then,thenCalledOrThrow = false
  // If the promise and x point to the same value, use TypeError to reject the promise as a cause.
  if (promise === x) {
    return reject(new TypeError('Chaining cycle detected for promise! '))}// Decide if x is a Promise. If so, pass resolve and reject from MyPromise to THEN;
  // Return a Promise object and take its result directly as the result of promise2
  if((x ! = =null) && ((typeof x === 'object') | | (typeof x === 'function'))) {
    try {
      then = x.then
      if (typeof then === 'function') { // typeof 

        //x.then(resolve, reject);
        then.call(x, function rs(y) {

          if (thenCalledOrThrow) return

          thenCalledOrThrow = true

          return resolvePromise(promise, y, resolve, reject)

        }, function rj(r) {

          if (thenCalledOrThrow) return

          thenCalledOrThrow = true

          return reject(r)

        })
      } else {

        return resolve(x)
      }
    } catch(e) {
      if (thenCalledOrThrow) return

      thenCalledOrThrow = true

      return reject(e)
    }
  } else {

    return resolve(x)
  }

}
Copy the code

The then function is last modified to:

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  let that = this;
  let promise2;
  // If the then argument is not function, then we need to ignore it
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : function(f) { return f};
  onRejected = typeof onRejected === 'function' ? onRejected : function(r) {throw r};
  This is a pity pity. This is a pity pity. This is a pity pity.
  // We have two arrays of on☆ darray
  if(that.status === 'pending') {
    return promise2 = new Promise(function(resolve, reject) {
      that.onFullfilledArray.push((value) = > {
        try {
          let x = onFulfilled(value);
          resolvePromise(promise2, x, resolve, reject)
        } catch(e) {
          return reject(e)
        }
      });
      
      that.onRejectedArray.push((value) = > {
        try {
          let x = onRejected(value);
          resolvePromise(promise2, x, resolve, reject)
        } catch(e) {
          returnreject(e) } }); })}if(that.status === 'fulfilled') {
    return promise2 = new MyPromise(function(resolve, reject) {
      try {
        let x = onFulfilled(that.value);
        // Handle multiple cases of then
        resolvePromise(promise2, x, resolve, reject)
      } catch(error) { reject(error); }})}if(that.status === 'rejected') {
    return new MyPromise(function(resolve, reject) {
      try {
        let x = onRejected(that.value);
        // Handle multiple cases of then
        resolvePromise(promise2, x, resolve, reject);
      } catch (error) {
        reject(error)
      }
      
    })
    
  }
}
Copy the code

Test it out:

new MyPromise((resolve, reject) = > {
  setTimeout((a)= > {
    resolve(1);
  }, 1000)
}).then((data) = > {
  console.log(data);
  return new MyPromise((res) = > {
    setTimeout((a)= > {
      res(2);
    },1000)
  })
}).then((res) = > {
  console.log(res);
})
/ / 1
/ / 2
Copy the code

The last

Promise GITHUB address. References:

“Promise/A+ specification” “Promise3” “achieve A perfect compliance with Promise/A+ specification of the Promise” “Analysis Promise internal structure, step by step to achieve A complete, can pass all Test case of the Promise class”