Promise A+ specification Chinese translation version address English original address

Interpretation of Promise Standard

1. Promise is like a state machine

There are three states:

  • pending

  • fulfilled

  • rejected

(1) The state of the promise object is initialized as pending

(2) When you call resolve(success), there will be pending => depressing

Reject => Reject; reject => Reject

2. Promise object methods

  1. There’s only one THEN method in the standard. There’s no catch, race, all, or even a constructor. The Promise standard only specifies the behavior of then methods on Promise objects. It doesn’t even specify how to construct a Promise object, and then doesn’t have a third argument supported in general implementations (Q, $Q, etc.), usually called onProgress

  2. The then method returns a new Promise. The Promise’s then method returns a new Promise instead of returning this, as explained more below

    promise2 = promise1.then(alert) promise2 ! = promise1// true
    Copy the code
  3. Different Promise implementations need to be interoperable

  4. The initial state of a Promise is pending, from which the state can be changed to depressing or Rejected. Once the state is determined, it cannot be changed to other states again. The process of state determination is called settle

3. Other ways to make Promises

In addition to the then method mentioned above, the Promise implemented in this paper will add catch, Race, All, Resolve, reject methods based on the standard

  1. Promise.prototype. Catch, in the chain writing method can catch the previous exception

    promise.catch(onRejected)
    / / equivalent to
    promise.then(null, onRrejected);
    
    / / note
    // onRejected cannot catch the exception in the current ondepressing
    promise.then(onFulfilled, onRrejected); 
    
    // Can be written as:
    promise.then(onFulfilled)
          .catch(onRrejected); 
    Copy the code
  2. Promise. Resolve, returns a fulfilled Promise object

    Promise.resolve('hello').then(function(value){
      console.log(value);
    });
    
    Promise.resolve('hello');
    / / equivalent to
    const promise = new Promise(resolve= > {
      resolve('hello');
    });
    Copy the code
  3. Promise.reject, returns a Promise object in the Rejected state

    Promise.reject(24);
    new Promise((resolve, reject) = > {
      reject(24);
    });
    Copy the code
  4. Promise. All, accept an array of Promise objects as arguments. Only the full Promise enters the fulfilled state will resolve

    const p1 = new Promise((resolve, reject) = > {
        resolve(1);
    });
    
    const p2 = new Promise((resolve, reject) = > {
        resolve(2);
    });
    
    const p3 = new Promise((resolve, reject) = > {
        resolve(3);
    });
    
    Promise.all([p1, p2, p3]).then(data= > { 
        console.log(data); // [1, 2, 3] The result order is the same as the order of the promise instance array
    }, err => {
        console.log(err);
    });
    Copy the code
  5. Promise. Race, receives an array of Promise objects as parameters. Race will continue to process as long as there is a Promise object that enters the fulfilled or Rejected state

    function timerPromisefy(delay) {
      return new Promise(function (resolve, reject) {
          setTimeout(function () {
              resolve(delay);
          }, delay);
      });
    }
    var startDate = Date.now();
    
    Promise.race([
        timerPromisefy(10),
        timerPromisefy(20),
        timerPromisefy(30)
    ]).then(function (values) {
        console.log(values); / / 10
    });
    Copy the code

Make a Promise one step at a time

Let’s implement a Promise step by step

The constructor

Since the standard does not specify how to construct a Promise object, we will also construct a Promise object in the same way that is currently common in general Promise implementations, which is used in ES6 native Promises:

// The Promise constructor receives an executor function that calls resolve and reject when it has completed an synchronous or asynchronous operation
var promise = new Promise(function(resolve, reject) {
  /* If the operation succeeds, call resolve and pass value. If the operation fails, call reject and pass reason */
})
Copy the code

The framework for implementing the constructor is as follows:

function Promise(executor) {
  var self = this
  self.status = 'pending' // Promise current state
  self.data = undefined  / / the value of the Promise
  self.onResolvedCallback = [] // The set of callback functions for Promise resolve, since more than one callback may be added to it before the Promise ends
  self.onRejectedCallback = [] // Set of callback functions for Promise reject, since more than one callback may be added to a Promise before it ends

  executor(resolve, reject) // Execute executor and pass in the parameters
}
Copy the code

The code above basically implements the body of the Promise constructor, but there are two problems:

  1. We passed the executor function two parameters, resolve and reject, which are not yet defined

  2. The executor can throw something like this, and if the executor fails, the Promise should be thrown with the value reject:

    new Promise(function(resolve, reject) {
      throw 2
    })
    Copy the code

So we need to define resolve and reject in the constructor:

function Promise(executor) {
  var self = this
  self.status = 'pending' // Promise current state
  self.data = undefined  / / the value of the Promise
  self.onResolvedCallback = [] // The set of callback functions for Promise resolve, since more than one callback may be added to it before the Promise ends
  self.onRejectedCallback = [] // Set of callback functions for Promise reject, since more than one callback may be added to a Promise before it ends

  function resolve(value) {
    // TODO
  }

  function reject(reason) {
    // TODO
  }

  try { // The Promise is wrapped in a try/catch block, with the catch value rejecting the Promise
    executor(resolve, reject) / / execution executor
  } catch(e) {
    reject(e)
  }
}
Copy the code

Next, we implement the resolve and reject functions

function Promise(executor) {
  // ...

  function resolve(value) {
    if (self.status === 'pending') {
      self.status = 'fulfilled'
      self.data = value
      // Execute the resolve callback, passing the value to the callback
      self.onResolvedCallback.forEach(callback= > callback(value))
    }
  }

  function reject(reason) {
    if (self.status === 'pending') {
      self.status = 'rejected'
      self.data = reason
      // Execute the reject callback, passing reason into the callback
      self.onRejectedCallback.forEach(callback= > callback(reason))
    }
  }

  // ...
}

Copy the code

It basically changes the state to the corresponding value, stores the corresponding value and reason on the data attribute of self, and then executes the corresponding callback function. The logic is very simple and there is not much explanation here.

Then method

The Promise object has a THEN method that registers callbacks after the Promise state is determined, and obviously the THEN method needs to be written on the prototype chain. The THEN method returns A Promise. The Promise/A+ standard does not require that the Promise returned be A new object, but the Promise/A standard explicitly states that then returns A new object, In the current Promise implementation, then almost always returns a new Promise object, so in our implementation, then also returns a new Promise object.

I think there’s a little bit of a contradiction in the standard:

If onResolved/onRejected in (onResolved, onRejected) returns a Promise, then promise2 will take the state and value of the Promise. But consider this code:

promise2 = promise1.then(function foo(value) {
  return Promise.reject(3)})Copy the code

If foo is running, the state of promise1 is resolved. If this is returned by then, promise2 and promise1 are the same object. In this case, the state of promise.reject (3) has been determined, and there is no way to take the state and result of promise.reject (3) as your own, because once the Promise state is determined, it cannot be changed to another state.

In addition, each Promise object can call the THEN method multiple times, and the state of the Promise returned by each call to then depends on the return value of the parameter passed in that call to THEN. Therefore, THEN cannot return this, because the Promise returned by then may be different each time.

Let’s implement the then method:

// The then method accepts two arguments, onResolved and onRejected, which are the callback after the Promise succeeds or fails
Promise.prototype.then = function (onResolve, onReject) {
  let self = this
  let promise2

  // According to the standard, if the argument to then is not function, then we need to ignore it
  onResolve = typeof onResolve==='function' ? onResolve : function(value){}
  onReject = typeof onReject==='function' ? onReject : function(reason){}
  
  if (self.status === 'pending') {
     return promise2 = new Promise(function(resolve, reject){})}if (self.status === 'fulfilled') {
    return promise2 = new Promise(function(resolve, reject){})}if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject){})}}Copy the code

There are three possible states for a Promise, which we deal with in three if blocks, each of which returns a New Promise.

As the standard tells us, the value of promise2 depends on the return value of the function in then:

promise2 = promise1.then(function(value) {
  return 4
}, function(reason) {
  throw new Error('sth went wrong')})Copy the code

If promise1 is resolved, promise2 will be resolved. If promise1 is rejected, promise2 will be rejected by new Error(‘ STH went wrong’) reject.

So we need to implement onResolved or onRejected in then and use the return value (x) to determine the outcome of promise2. If onResolved/onRejected returns a Promise, Promise2 directly takes the result of this Promise:

// The then method accepts two arguments, onResolved and onRejected, which are the callback after the Promise succeeds or fails
Promise.prototype.then = function (onResolve, onReject) {
  let self = this
  let promise2

  // According to the standard, if the argument to then is not function, then we need to ignore it
  onResolve = typeof onResolve==='function' ? onResolve : function(value){}
  onReject = typeof onReject==='function' ? onReject : function(reason){}
  
  if (self.status === 'fulfilled') {
    // If the state of promise1(this is this/self) is fulfilled, we will call onResolved
    // We wrap it in a try/catch block because of the possibility of throwing
    return promise2 = new Promise(function(resolve, reject){
      try {
        var x = onResolved(self.data)
        if (x instanceof Promise) { // If onResolved returns a Promise object, take its outcome as the outcome of promise2
          x.then(resolve, reject)
        } else {
          resolve(x) // Otherwise, use its return value as the result of promise2}}catch (e) {
        reject(e) // If an error occurs, the captured error is used as the result of promise2}})}// Call onRejected (); // Call onRejected (); // Call onRejected ()
  if (self.status === 'rejected') {
    return promise2 = new Promise(function(resolve, reject){
      try {
        var x = onRejected(self.data)
        if (x instanceof Promise) {
          x.then(resolve, reject)
        } else {
          resolve(x)
        }
      } catch (e) {
        reject(e)
      }
    })
  }

  if (self.status === 'pending') {
    // If the Promise is still pending, we can't decide onResolved or onRejected,
    // We can't be sure what to do until the state of the Promise is confirmed.
    // So we need to put our two case processing logic as callback in the promise1 callback array (in this case this/self)
    // The logic itself is almost identical to that in the first if block, without much explanation here
    return promise2 = new Promise(function(resolve, reject){
      self.onResolvedCallback.push(function(value) {
        try {
          var x = onResolved(self.data)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          } else {
            resolve(x)
          }
        } catch (e) {
          reject(e)
        }
      })

      self.onRejectedCallback.push(function(reason) {
        try {
          var x = onRejected(self.data)
          if (x instanceof Promise) {
            x.then(resolve, reject)
          } else {
            resolve(x)
          }
        } catch (e) {
          reject(e)
        }
      })
    })
  }

}

// We implement a catch method for the rest of the article
Promise.prototype.catch = function(onRejected) {
  return this.then(null, onRejected)
}
Copy the code

So far, we’ve basically implemented everything in the Promise standard, but there are a few issues:

  1. There needs to be seamless interaction between different Promise implementations, that is, between the Promise of Q, the Promise of ES6, and the Promise we implement, and other Promise implementations, that should and should call each other seamlessly, such as:
    // Here we use MyPromise to represent our Promise
    new MyPromise(function(resolve, reject) { // We implemented the Promise
      setTimeout(function() {
        resolve(42)},2000)
    }).then(function() {
      return new Promise.reject(2) / / ES6 Promise
    }).then(function() {
      return Q.all([ / / Q Promise
        new MyPromise(resolve= >resolve(8)), // We implemented the Promise
        new Promise.resolve(9), / / ES6 Promise
        Q.resolve(9) / / Q Promise])})Copy the code

    The code we implemented earlier did not deal with such logic. We only judged whether the return value of onResolved/onRejected is the instance of our Promise and did not make any other judgment. Therefore, the code above cannot run correctly in our Promise at present.

  2. There is no way to handle the following code:
    new Promise(resolve= >resolve(8))
      .then()
      .then()
      .then(function foo(value) {
        alert(value)
      })
    Copy the code

    The correct behavior would be to alert 8, and if we took our Promise and ran the code above, it would alert undefined. This behavior is called penetration, meaning that the value of 8 will pass through both THEN (Promise is more accurate) to foo in the last THEN, become its argument, and eventually alert 8.

Let’s first deal with the simple case of value penetration

Penetration of the Promise value

If you look at it, you’ll see that we want the following code

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

The behavior of this code is the same

new Promise(resolve= >resolve(8))
  .then(function(value){
    return value
  })
  .catch(function(reason){
    throw reason
  })
  .then(function(value) {
    alert(value)
  })
Copy the code

Function (value) {return value} and function(reason) {throw reason} are the default values for the two arguments to then.

So we just need to change the part of “onResolved and onRejected” in then to the following:

onResolved = typeof onResolved === 'function' ? onResolved : function(value) {return value}
onRejected = typeof onRejected === 'function' ? onRejected : function(reason) {throw reason}
Copy the code
So Promise’s magic value penetration isn’t so black magic, it’s just the then default argument that passes the value back or throws it
Interactions between different promises

As for the interaction between different promises, the standard specifies how to use the value returned by the arguments of then to determine the state of promise2. All we need to do is translate the standard into code.

Here’s a quick explanation of the criteria:

So we’re going to treat the onResolved/onRejected return value X as a possible Promise object and call the THEN method on X in the safest way. If we do this, Then different promises can interact with each other. While the standard to be on the safe side, even if x returned with a then properties but it doesn’t follow the Promise of the standard objects (such as the two parameters in this x, then it is called, synchronous or asynchronous invocation (PS, in principle, then the two parameters need to be asynchronous calls, talk about below), or the wrong call them again, Or then is not a function at all), can be handled as correctly as possible.

I think the reason why different Promise implementations need to be able to interact with each other is obvious. Promises are not a standard from the beginning of JS, and different third-party implementations don’t know each other. If you use a library that encapsulates a Promise implementation, Imagine if it didn’t interact with your own Promise implementation…

I encourage you to read the following code against the standard, which is quite detailed, so you should be able to find similar code in any Promise implementation:

/ * resolvePromise function is decided according to the value of x promise2 state function is also the standard of [Promise Resolution Procedure] (https://promisesaplus.com/#point-47) X is' promise2 = promise1. Then (onResolved, The 'resolve' and 'reject' returns in 'onRejected' and 'resolved' are the 'executor' arguments of 'promise2' that are difficult to mount anywhere else. I believe that you will be able to compare the standard to the code, here is only to mark the corresponding position of the code in the standard, only in the necessary places to do some explanation */
function resolvePromise (promise2, x, resolve, reject) {

  let then 
  let thenCalledOrThrow = false

  if (promise2 === x) { // Corresponds to section 2.3.1 of the standard
    Promise2 ===x (2.3.1) {// When is promise2===x triggered
    // the condition promise === x, equivalent to the promise. Then then will wait for the promise after return, so that then will wait for itself, always waiting.
    / * * such as: Let p1 = new Promise(function(resolve, reject) {setTimeout(() => {reject(1.1)}, 1000) }) let p1then = p1.then(function (value) { return p1then }, function (err) { return p1then }) p1then.then(value => { console.log('value:', value) }, err => { console.log('err:', Err)}) this code triggers promise2 === x, we are not always waiting for promises, according to the standard specification, here we need to throw 'Chaining cycle detected for promises', The 'circular reference' error message */
    reject(new TypeError('Chaining cycle detected for promise! '))
    return
  }

  if (x instanceof Promise) { // Corresponds to section 2.3.2 of the standard
    // If the state of x is not yet determined, it is possible to determine the final state and value by a thenable
    // It is necessary to do something about it, rather than assume it will be resolved by a "normal" value
    if (x.status === 'pending') {
      x.then(value= > {
        resolvePromise(promise2, value, resolve, reject)
      }, err => {
        reject(err)
      })
    }  else { // But if the state of the Promise is already established, it must have a "normal" value, not a thenable, so take its state directly
      x.then(resolve, reject)
    }
    return
  }

  if((x ! = =null) && ((typeof x === 'function') | | (typeof x === 'object'))) {
    try {
      // 2.3.3.1 Because x. teng may be a getter, in which case multiple reads may cause side effects
      // To determine its type, and to call it, that is two reads
      then = x.then //because x.then could be a getter
      if (typeof then === 'function') { / / 2.3.3.3
        then.call(x, value => { / / 2.3.3.3.1
          if (thenCalledOrThrow) return // 2.3.3.3.3 That is, the result of the execution of the three shall prevail
          thenCalledOrThrow = true
          resolvePromise(promise2, value, resolve, reject) / / 2.3.3.3.1
          return
        }, err => { / / 2.3.3.3.2
          if (thenCalledOrThrow) return // 2.3.3.3.3 That is, the result of the execution of the three shall prevail
          thenCalledOrThrow = true
          reject(err)
          return})}else { / / 2.3.3.4
        resolve(x)
      }
    } catch (e) { / / 2.3.3.2
      if (thenCalledOrThrow) return // 2.3.3.3.3 That is, the result of the execution of the three shall prevail
      thenCalledOrThrow = true
      reject(e)
      return}}else { / / 2.3.4
    resolve(x)
  }

}
Copy the code

We then use the call to this function to replace several places in then that determine whether x is a Promise object, as shown in the full code below.

In principle, the promise. Then (onResolved, onRejected) functions need to be called asynchronously.

In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack.

So we need to change our code a little bit by adding setTimeout(fn, 0) in four places.

Tip: If you are not familiar with the js execution stack, js single thread, and eventLoop, you can Google the relevant information. Later I will also write an article on JS execution stack, JS single thread, eventloop. In the following code, I will also briefly write some reasons for adding setTimeout.

function Promise (executor) {
  let self = this
  self.status = 'pending' //Promise current state
  self.data = undefined // The current Promise value
  self.onResolvedCallback = [] // Set of callback functions for Promise resolve
  self.onRejectedCallback = [] // A collection of callback functions for Promise reject

  function resolve (value) { // value Final value received in successful state
    if (value instanceof Promise) {
      value.then(resolve, reject)
      return
    }

    // Why resolve and setTimeout?
    // 2.2.4 onFulfilled and onRejected are only allowed to run if the execution Context stack contains only platform code.
    // Note 1 The platform code here refers to the engine, environment, and promise implementation code. In practice, ensure that the onFulfilled and onRejected methods are executed asynchronously and should be executed in a new execution stack after the event loop in which the THEN method is called.
    setTimeout(function(){
      // Call the resolve callback corresponding to the ondepressing function
      if (self.status === 'pending') {
        // This can only be fulfilled by a pending state.
        self.status = 'fulfilled'
        self.data = value
        // Execute the resolve callback, passing the value to the callback
        self.onResolvedCallback.forEach(callback= > callback(value))
      }
    })
  }

  function reject (reason) { // reason Indicates the rejection reason received in the failed state
    setTimeout(function(){
      // Call the reject callback corresponding to the onRejected function
      if (self.status === 'pending') {
        // Only the pending state => Rejected state
        self.status = 'rejected'
        self.data = reason
        // Execute the reject callback, passing reason into the callback
        self.onRejectedCallback.forEach(callback= > callback(reason))
      }
    })
  }

  try {
    executor(resolve, reject)
  } catch (e) {
    reject(e)
  }
  
}

/** * (this is a big pity /rejected) * @param {function} * @return {function} newPromsie returns a new Promise object */
Promise.prototype.then = function (onResolve, onReject) {
  let self = this
  let promise2

  // The default value of processing parameters ensures that the parameters can be executed later
  onResolve = typeof onResolve==='function' ? onResolve : function(value){return value}
  onReject = typeof onReject==='function' ? onReject : function(reason){throw reason}
  
  if (self.status === 'pending') { / / wait state
     return promise2 = new Promise(function(resolve, reject){
      
      // Ondepressing /onRejected will be collected temporarily in the collection when asynchronously calling resolve/ Rejected
      self.onResolvedCallback.push(function(value){
        try {
          let x = onResolve(value)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })

      self.onRejectedCallback.push(function(reason){
        try {
          let x = onReject(reason)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    })
  }

  // Then this is a big pity /REJECTED state.
  / / reasons:
  // The onFulfilled and onRejected methods should be executed asynchronously (and should be executed in the new stack after the event loop where the THEN method is called), so add setTimeout to resolve
  // This is a big pity/hoped-in state, which is a pity /REJECTED state. // This is a big pity/hoped-in state, which is a pity/Hoped-in state. This will be a big pity /onRejected state), so make sure that onFulfilled/onRejected state is also FULFILLED asynchronously

  // The 2.2.6 specification is also the reason to add setTimeout to resolve
  // This is a big pity/onFulfilled asynchronously. // This is a big pity /onRejected asynchronously

  // Call p1.then several times as in the following scenario
  Then ((value) => {// This is a big pity
  // console.log(value); // resolve
  // // console.log(p1.status); // fulfilled
  // p1. Then (value => {// again p1. Then there is a depressing state to follow, so we should also make sure that the onfule taille is executed asynchronously
  // console.log(value); // 'resolve'
  / /});
  // console.log(' currently executing sync code in stack ');
  // })
  // console.log(' sync code in global execution stack ');
  //
  if (self.status === 'fulfilled') { / / success
    return promise2 = new Promise(function(resolve, reject){
      setTimeout(function(){
        try {
          let x = onResolve(self.data)
          resolvePromise(promise2, x, resolve, reject) // The new promise resolve will return the value of onFulfilled
        } catch (e) {
          reject(e) // This is a big pity; then(onFulfilled, onRejected);}},0)})}if (self.status === 'rejected') { / / failure mode
    return promise2 = new Promise(function(resolve, reject){
      setTimeout(function(){
        try {
          let x = onReject(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      },0)}}}Copy the code

Other Promise methods

Promise.prototype.catch,

The function is to catch previous exceptions in chained writing

The implementation is similar to the second argument to THEN

Like this:

// Catch the exception that onFulfilled/onRejected throws when used in the promise method chain
Promise.prototype.catch = function (onReject) {
  return this.then(null, onReject)
}
Copy the code
Promise.resolve

Return a fulfilled Promise object

// Return a fulfilled Promise object
Promise.resolve = function (value) {
  return new Promise(function(resolve, reject){resolve(value)})
}
Copy the code
Promise.reject

Return a Promise object in the Rejected state

// Return a Promise object in the Rejected state
Promise.reject = function (reason) {
  return new Promise(function(resolve, reject){reject(reason)})
}
Copy the code
Promise.all

Accept an array of Promise objects as parameters. Only when all promises enter the fulfilled state will the promise continue. The subsequent processing is usually used to process multiple parallel asynchronous operations

/** * Promise. All Promise is processed in parallel * Parameters: array of Promise objects as parameters * return value: This is a big pity. Return a Promise instance * This will be FulFilled when all the Promise objects in this array enter the FulFilled state. * /
Promise.all = function (promises) {
  return new Promise((resolve, reject) = > {
    let values = []
    let count = 0
    promises.forEach((promise, index) = > {
      promise.then(value= > {
        console.log('value:', value, 'index:', index)
        values[index] = value
        count++
        if (count === promises.length) {
          resolve(values)
        }
      }, reject)
    })
  })
}
Copy the code
Promise.race

Race receives an array of Promise objects as parameters. Race will continue with the follow-up processing as long as one promise object enters the fulfilled or Rejected state

/** * promise.race * argument: accepts an array of Promise objects as arguments * returns a value: Return a Promise instance * As long as one of the Promise objects enters the FulFilled or Rejected state, the subsequent processing will continue (depending on which is faster) */
Promise.race = function (promises) {
  return new Promise((resolve, reject) = > {
      promises.forEach((promise) = > {
         promise.then(resolve, reject);
      });
  });
}
Copy the code
At this point, we have implemented a Promise, the complete code is as follows:
function Promise (executor) {
  let self = this
  self.status = 'pending' //Promise current state
  self.data = undefined // The current Promise value
  self.onResolvedCallback = [] // Set of callback functions for Promise resolve
  self.onRejectedCallback = [] // A collection of callback functions for Promise reject

  function resolve (value) { // value Final value received in successful state
    if (value instanceof Promise) {
      value.then(resolve, reject)
      return
    }

    // Why resolve and setTimeout?
    // 2.2.4 onFulfilled and onRejected are only allowed to run if the execution Context stack contains only platform code.
    // Note 1 The platform code here refers to the engine, environment, and promise implementation code. In practice, ensure that the onFulfilled and onRejected methods are executed asynchronously and should be executed in a new execution stack after the event loop in which the THEN method is called.
    setTimeout(function(){
      // Call the resolve callback corresponding to the ondepressing function
      if (self.status === 'pending') {
        // This can only be fulfilled by a pending state.
        self.status = 'fulfilled'
        self.data = value
        // Execute the resolve callback, passing the value to the callback
        self.onResolvedCallback.forEach(callback= > callback(value))
      }
    })
  }

  function reject (reason) { // reason Indicates the rejection reason received in the failed state
    setTimeout(function(){
      // Call the reject callback corresponding to the onRejected function
      if (self.status === 'pending') {
        // Only the pending state => Rejected state
        self.status = 'rejected'
        self.data = reason
        // Execute the reject callback, passing reason into the callback
        self.onRejectedCallback.forEach(callback= > callback(reason))
      }
    })
  }

  try {
    executor(resolve, reject)
  } catch (e) {
    reject(e)
  }
  
}

/** * resolve: * 1. The normal value * 2. The promise object * 3

@param {promise} promise2 promise1. Then new promise object * @param {[type]} x * @param {[type]} resolve Resolve method of promise2 * @param {[type]} reject Reject method of promise2 */
function resolvePromise (promise2, x, resolve, reject) {

  let then 
  let thenCalledOrThrow = false // Avoid multiple calls

  if (promise2 === x) { // If the x returned from ondepressing is promise2, this will cause a circular reference error
    reject(new TypeError('Chaining cycle detected for promise! '))
    return
  }

  // If x is a promise object we wrote ourselves
  if (x instanceof Promise) {
    if (x.status === 'pending') { // Wait until x is executed or rejected and value is resolved
      x.then(value= > {
        resolvePromise(promise2, value, resolve, reject)
      }, err => {
        reject(err)
      })
    }  else { // If x is already in the execute/reject state (the value has been resolved to a normal value), execute the pass promise with the same value
      x.then(resolve, reject)
    }
    return
  }

  // If x is an object or function
  if((x ! = =null) && ((typeof x === 'function') | | (typeof x === 'object'))) {
    try {
      then = x.then //because x.then could be a getter
      if (typeof then === 'function') { // Is the thenable object (object/function with then method)
        then.call(x, value => {
          if (thenCalledOrThrow) return
          thenCalledOrThrow = true
          resolvePromise(promise2, value, resolve, reject)
          return
        }, err => {
          if (thenCalledOrThrow) return
          thenCalledOrThrow = true
          reject(err)
          return})}else { // indicates a normal object/function
        resolve(x)
      }
    } catch (e) {
      if (thenCalledOrThrow) return
      thenCalledOrThrow = true
      reject(e)
      return}}else {
    resolve(x)
  }

}

/** * (this is a big pity /rejected) * @param {function} * @return {function} newPromsie returns a new Promise object */
Promise.prototype.then = function (onResolve, onReject) {
  let self = this
  let promise2

  // The default value of processing parameters ensures that the parameters can be executed later
  onResolve = typeof onResolve==='function' ? onResolve : function(value){return value}
  onReject = typeof onReject==='function' ? onReject : function(reason){throw reason}
  
  if (self.status === 'pending') { / / wait state
     return promise2 = new Promise(function(resolve, reject){
      
      // Ondepressing /onRejected will be collected temporarily in the collection when asynchronously calling resolve/ Rejected
      self.onResolvedCallback.push(function(value){
        try {
          let x = onResolve(value)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })

      self.onRejectedCallback.push(function(reason){
        try {
          let x = onReject(reason)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      })
    })
  }

  // Then this is a big pity /REJECTED state.
  / / reasons:
  // The onFulfilled and onRejected methods should be executed asynchronously (and should be executed in the new stack after the event loop where the THEN method is called), so add setTimeout to resolve
  // This is a big pity/hoped-in state, which is a pity /REJECTED state. // This is a big pity/hoped-in state, which is a pity/Hoped-in state. This will be a big pity /onRejected state), so make sure that onFulfilled/onRejected state is also FULFILLED asynchronously

  // The 2.2.6 specification is also the reason to add setTimeout to resolve
  // This is a big pity/onFulfilled asynchronously. // This is a big pity /onRejected asynchronously

  // Call p1.then several times as in the following scenario
  Then ((value) => {// This is a big pity
  // console.log(value); // resolve
  // // console.log(p1.status); // fulfilled
  // p1. Then (value => {// again p1. Then there is a depressing state to follow, so we should also make sure that the onfule taille is executed asynchronously
  // console.log(value); // 'resolve'
  / /});
  // console.log(' currently executing sync code in stack ');
  // })
  // console.log(' sync code in global execution stack ');
  //
  if (self.status === 'fulfilled') { / / success
    return promise2 = new Promise(function(resolve, reject){
      setTimeout(function(){
        try {
          let x = onResolve(self.data)
          resolvePromise(promise2, x, resolve, reject) // The new promise resolve will return the value of onFulfilled
        } catch (e) {
          reject(e) // This is a big pity; then(onFulfilled, onRejected);}},0)})}if (self.status === 'rejected') { / / failure mode
    return promise2 = new Promise(function(resolve, reject){
      setTimeout(function(){
        try {
          let x = onReject(self.data)
          resolvePromise(promise2, x, resolve, reject)
        } catch (e) {
          reject(e)
        }
      },0)}}}/** * Promise. All Promise is processed in parallel * Parameters: array of Promise objects as parameters * return value: This is a big pity. Return a Promise instance * This will be FulFilled when all the Promise objects in this array enter the FulFilled state. * /
Promise.all = function (promises) {
  return new Promise((resolve, reject) = > {
    let values = []
    let count = 0
    promises.forEach((promise, index) = > {
      promise.then(value= > {
        console.log('value:', value, 'index:', index)
        values[index] = value
        count++
        if (count === promises.length) {
          resolve(values)
        }
      }, reject)
    })
  })
}

/** * promise.race * argument: accepts an array of Promise objects as arguments * returns a value: Return a Promise instance * As long as one of the Promise objects enters the FulFilled or Rejected state, the subsequent processing will continue (depending on which is faster) */
Promise.race = function (promises) {
  return new Promise((resolve, reject) = > {
      promises.forEach((promise) = > {
         promise.then(resolve, reject);
      });
  });
}

// Catch the exception that onFulfilled/onRejected throws when used in the promise method chain
Promise.prototype.catch = function (onReject) {
  return this.then(null, onReject)
}

// Return a fulfilled Promise object
Promise.resolve = function (value) {
  return new Promise(function(resolve, reject){resolve(value)})
}

// Return a Promise object in the Rejected state
Promise.reject = function (reason) {
  return new Promise(function(resolve, reject){reject(reason)})
}

Promise.deferred = Promise.defer = function() {
  var defer = {}
  defer.promise = new Promise(function(resolve, reject) {
    defer.resolve = resolve
    defer.reject = reject
  })
  return defer
}

try {
  module.exports = Promise
} catch (e) {
}

// Promise core content complete test method
let promisesAplusTests =  require("promises-aplus-tests")
promisesAplusTests(Promise.function(err){
  console.log('err:', err);
  // Complete; The output is in the console. Or check 'err' for the number of failures.
})
Copy the code

Tip: full code + test demo file address

test

How do we make sure that the Promise we implement meets the standards? Promise comes with a test script that simply requires us to expose a deferred method (exports.Deferred method) in a CommonJS module, as shown at the end of the code above. Then execute the test by executing the following code:

npm i promises-aplus-tests
node ./Promise.js
Copy the code

Related knowledge reference materials

Promises/A+ Promises/A+

Promises/A+ Promises/A+

Parse the Promise internals