We’ve already implemented some of the basics of the Promise library in the last article, so let’s build on that. This article describes the complete version of the Proimse library code that has been posted to personal Github

1. Add the static method Deferred to promises

We added a static method deferred to the Promise constructor (this method does not exist in the official Promise definition, but is a slightly different way of writing asynchronous logic than regular promises). Promise.deferred is used as follows:

let dfd = Promise.deferred();
$.ajax({
  type: 'get'.url: 'xxx'
})
  .done(res= > dfd.resolve(res))
  .fail(err= > dfd.reject(err));

dfd.promise.then(res= > console.log(res));
Copy the code

The promise.deferred method reduces the number of new promises and the number of nested functions compared to the regular writing method, which is a bit more concise, but this usage is now largely obsolete because of the better async-await syntax

So let’s implement the promise.Deferred method. Why should we implement it? Because we will need to test whether the Promise library complies with Promises/A+ through A test library, and the test library requires us to provide the Promise.Deferred as the test entry

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

As you can see, the internal logic of the Deferred method is not complicated, so you can use the test library to test it

2. Promise-aplus-tests

Promise comes with A test script that will help you test whether Promises/A+ Promise libraries are Promises/A+ compliant. Here’s how to do it:

1) Install the library

npm install -g promises-aplus-tests

2) Run the test library to test the Promise code

promises-aplus-tests promise.js

This promise.js is replaced with an entry file for the library I wrote. Note that the promise needs to be printed at the end of the file in commonJS syntaxmodule.exports = PromiseThe esModule syntax output is not accepted when run through global commands

Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+ : Promises/A+

3. The catch

We are used to using the catch method to catch errors thrown in previous promises

let promise = new Promise((resolve, reject) = > {
  reject(100)}); promise.then(res= > 
  console.log(res)
).catch(err= > 
  console.log(err)
);
Copy the code

Then (null, err => console.log(err))

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

4. Pass in the promise scenario when you call resolve

The Promise libraries we have implemented so far are missing a scenario where one Promise object executes the resolve method and another is passed in. The scenario is as follows:

let promise = new Promise((resolve, reject) = > {
  resolve(new Promise((_resolve, _reject) = > {
    setTimeout(() = > {
      _resolve(123);
    }, 2000)})); }); promise.then(res= > {
  console.log(res);
}, err= > {
  console.log(err);
})
Copy the code

According to the current logic, when the resolve method is executed, the newly created promise2 object will be assigned to the value of the outer promise, so the outer promise state will be fulfilled. This is a big pity. Then (ondepressing, onRecjected) the ondepressing function will be invoked immediately when they are delivered (i.e. SetTimeout 2s in the promise2 executor has no effect on the promise implementation). Moreover, the parameter received by the ondepressing function is promisE2 in the pending state, which is obviously not in accordance with the promise definition.

The correct logic here is that if the resolve parameter is a new promise object (promise2), the external promise needs to wait for the state of the promise2 to change from pending to pity or Rejected. The state is also changed, and the value of promise2 is inherited as its value or the reason of promise2 is inherited as its reason. Then, the onFulfilled or onRejected passed in the THEN function will be performed.

That is, if a 2s timer is set for promise2, the external promise will also wait for the state of promise2 to change 2s before performing its onFulfilled or onRejected method

So let’s modify the resolve method in the Promise constructor a little bit so that it also works when it receives a Promise object

function Promise(executor) {
  // ...
  const resolve = value= > {
    if (value instanceof Promise) {
      value.then(resolve, reject);
      return;
    }

    if (this.status === 'PENDING') {
      // ...}};const reject = reason= > {
    // ...
  };
  
  try {
    executor(resolve, reject);
  } catch(err) { reject(err); }}Copy the code

Then (resolve, reject) to wait for the state of the promise2 object to which the value points. Then, the resolve and Reject methods of the current promise object are passed in as parameters. When the promise2 object changes to depressing or Rejected, the resolve and Reject methods of the current promise object will be called respectively to change its state. The value of the promise2 can be used as a resolve parameter, and the reason of the promise2 can be used as a reject parameter. Then the promise inherits the value and reason of the promise2.

The ondepressing method in promise. Then will execute 2 seconds later, and the parameter it receives is the internal value of promise2:123.

5. Promise. Resolve and Promise. Reject

There are two static methods on the Promise: resolve and reject, which are used to create a Promise in the fulfilled state and rejected state, respectively. This is relatively simple, so let’s go straight to the internal logic

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

6. Promise.all

Promise.all is a method that executes multiple promises concurrently, as shown in the following example

let p1 = new Promise(resolve= > {resolve(100)});
let p2 = new Promise(resolve= > {
  setTimeout(() = > {
    resolve(200);
  }, 1000);
});
let p3 = 300;

Promise.all([p1, p2, p3]).then(res= > {
  console.log(res);	/ / [100, 200, 300]
});
Copy the code

The promise. all method takes an array of arguments and returns a new Promise, so you can call.then,.catch on it.

This is a big pity. If all the elements in the passed array are fulfilled promise objects or common values, the new promise state will be fulfilled. If there is an Error object or a Promise with the Rejected state in the passed array, The method returns a new promise state of Rejected. Internal implementation is as follows:

function isPromise(value) {
  if (typeof value === 'object'&& value ! = =null || typeof value === 'function') {
    return typeof value.then === 'function';
  } else {
    return false; }}Promise.all = function all(promises) {
  // promise. all returns a Promise, so return new Promise
  return new Promise((resolve, reject) = > {
    let arr = [];	// The result set after processing will be returned as the value of proimse
    let count = 0;	// Count how many promises have been processed

    function processData(index, value) {
      arr[index] = value;
      if (++count === promises.length) {
        // When the number of elements in the result set equals the length of the array passed in, all promises have been processed. The returned Promise objects can be changed to the depressing state
        // And the result set is returned as the value of the promiseresolve(arr); }}for (let i=0; i<promises.length; i++) {
      let current = promises[i];
      if (isPromise(current)) {
        // If it is a Promise object, call its then method and wait for the promise state to change
        current.then(data= > {
          processData(i, data);
        }, err= > 
          reject(err) // If any of the parameters passed in a promise object are Rejected, change the returned promise object to Rejected)}else {
        // If it is a normal value, it goes directly to the result setprocessData(i, current); }}})}Copy the code

As shown above, a count variable is used to calculate how many elements have been processed. If all have been processed, the returned promises are changed to the fuflilled state. If any of the promises in the processing are in the Rejected state, Sets the returned promise to the Rejected state

7. Promise.race

The promise.race method takes an array, processes the multiple Promise/normal values in the array and returns a new Promise object, which takes the state of the Promise object with the fastest state change of the multiple Promise objects as the final result, as shown in the following example

let p1 = new Promise(resolve= > {
  setTimeout(() = > {
    resolve(100)},500);
});
let p2 = new Promise(resolve= > {
  resolve(200);
});
Promise.race([p1, p2]).then(res= > {
  console.log(res);	/ / 200
})
Copy the code

As shown above, if P1 has a delayed operation and P2 does not, and P2’s state changes earlier, the Promise object returned by promise. race will take p2’s result as the result. So let’s implement the internal logic of promise.race

function isPromise(value) {
  if (typeof value === 'object'&& value ! = =null || typeof value === 'function') {
    return typeof value.then === 'function';
  } else {
    return false; }}Promise.race = function race(promises) {
  return new Promise((resolve, reject) = > {
    for (let i=0; i<promises.length; i++) {
      let current = promises[i];
	  if (isPromise(current)) {
	    // If current is a promise object, wait for its state to change, and then return a promise with the same state as current
	    current.then(resolve, reject);
	  } else {
	    // If current is the normal value, the promise to be returned will be replaced with a fulfilled state, and this normal value is the value of proimseresolve(current); }}})}Copy the code

So far, our Promise library is basically implemented, there are some less commonly used Promise API is not implemented, in addition, there may be some more complex scenarios are not considered, we can do extension based on this basis.

The resources

  • Promises/A + specification
  • Promises/A+ Test Tools
  • Promise library complete implementation code