What problem did Promise solve

Before promises, multiple asynchronous operations with nested relationships tended to look like this: layer upon layer of callbacks (callback hell) to satisfy that nesting relationship. Real-world code must have a lot of logic before each asynchronous operation, and as the amount of code increases, it becomes less and less maintainable.

const fs = require('fs');

fs.readFile('./a.txt'.'utf8'.(err, data) = > {
  // ...
  fs.readFile(data, 'utf8'.(err, data) = > {
    // ...
    fs.readFile(data, 'utf8'.(err, data) = > {
      console.log(data);
      // ...})})})Copy the code

Logical relation:

With Promise in place, we can rewrite the above code like this:

const readFIlePromise = filename= > {
  return new Promise((resolve, reject) = > {
    fs.readFile(filename, 'utf8'.(err, data) = > {
      if (err) reject(err);
      resolve(data);
    })
  })
}

readFIlePromise('./a.txt')
  .then(data= > {
    // ...
    return readFIlePromise(data);
  })
  .then(data= > {
    // ...
    return readFIlePromise(data);
  })
  .then(data= > {
    console.log(data);
    // ...
  })
  .catch(err= > {
    console.error(err);
  })
Copy the code

Logical relation:

Look,PromiseSince the advent of the chain-call script overwriting the nested script is not much clearer


How to fulfill a Promise

Many Promise libraries, such as Bluebird and ES6-PROMISE, are based on the Promise A+ specification. The most obvious benefit of implementing the Promise A+ specification is that you can call each other. So study Promise, still suggest read A+ specification again.

Promise A + specification

Let’s go back to Promise

Let’s take a look back at Promise. You need to know how to use it before you know how to write it right. Look at the code:

const p1 = new Promise((resolve, reject) = > {
  console.log('CREATE p1');
  resolve('p1');
});

const p2 = p1.then(data= > {
  console.log('p1-data', data);
  throw new Error('Wrong');
});

const p3 = p2.then(data= > {
  console.log('p2-data', data);
}).catch(err= > {
  console.log('p2-error', err);
})

// create p1
// p1-data p1
// p2-error error: error
Copy the code

Just from the code level above, we can seePromiseIt has the following characteristics:

  • The process of instantiation needs to receive a function (temporarily calledexecutor) will be executed internally immediatelyexecutorThis function;
  • executorExecute it if it succeedsresolveAnd pass on the success data. Failure/error is executedreject, sends an error message;

In combination withPromise A+Norms, we can concludePromiseFeatures:

  • PromiseThere are only three states:pending,fulfilled,rejected;
    [ specification 2.1 ] \color{blue} {[specification 2.1]}
  • new PromiseYou need to pass in an executorexecutor.executorWill be executed immediately;
  • executorReceives two parameters, respectivelyresolve,reject;
  • The default ispending.pendingThe state can be converted tofulfilledThe state orrejectedState, once the state is changed, it cannot be changed;
    [ specification 2.1.1 ] \ color {blue} {2.1.1 [specification]}
  • fulfilledThere must be one statevalueTo save successful results;
    [ specification 2.1.2 ] \ color {blue} {2.1.2 [specification]}
  • rejectedThere must be one statereasonTo save the result of failure;
    [ specification 2.1.3 ] \ color {blue} {2.1.3 [specification]}
  • PromiseYou have to have onethenMethod,thenMethod receives two parameters, one for a successful callbackonFulfilled, a callback after a failureonRejected;
    [ specification 2.2 ] \ color {blue} {} [2.2]
  • PromiseIf successful, the command is executedthenMethod successfully callbackonFulfilledAnd,valueWill be used as an argument for a successful callback;
  • PromiseIf it fails, it will be executedthenMethod failure callbackonRejectedAnd,reasonWill be used as an argument for a failed callback;

Tips:

It’s normal to be confused by the words. I was confused, too. We can see the following implementation directly, according to the implementation and then compare the Promise A+ specification is easier to understand:

1. Implement a base version firstPromise

Based on the above features, let’s take our time and implement a basic version of Promise:

// Define three states: Pending, depressing, and Rejected
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class Promise {
  /** * actuator *@param {Function} executor 
   */
  constructor(executor) {
    this.status = PENDING;          // The initialization is pending
    this.value = undefined;         // Save successful data
    this.reason = undefined;        // Cause of save failure

    // Execute this function in success state
    const resolve = data= > {
        if(this.status === PENDING) {
            this.status = FULFILLED;
            this.value = data; }}// Failed to execute this function
    const reject = reason= > {
        if(this.status === PENDING) {
            this.status = REJECTED;
            this.reason = reason; }}try {
      // Execute executor immediately when new
      executor(resolve, reject);
    } catch(err) {
      We also throw reject for errors thrown by the executorreject(err); }}then(onFulfilled, onRejected) {
    if(this.status === FULFILLED) {
      onFulfilled(this.value);
    };
    
    if(this.status === REJECTED) {
      onRejected(this.reason); }}}Copy the code

testDemo 1

new Promise((resolve, reject) = > {
  resolve(1);
})
  .then(data= > {
    console.log('success', data);
  }, err= > {
    console.log('err', err);
  })

// success 1
Copy the code

The base version of Promise is complete, but it only supports the most basic functionality – passing normal values (success/failure).

testDemo 2

new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve(1);
  }, 1000)
})
  .then(data= > {
    console.log('success', data);
  }, err= > {
    console.log('err', err);
  })

// Do not print anything
Copy the code

Demo 2 prints nothing because the executor function passed in by New Promise takes 1 second to become successful, whereas the then method executes immediately after the executor synchronization code completes (the Promise state is still pending, So neither of the then callbacks will be executed.

2. Upgrade the base versionPromise

// Define three states: Pending, depressing, and Rejected
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

class Promise {
  /** * actuator *@param {Function} executor 
   */
  constructor(executor) {
    this.status = PENDING;          // The initialization is pending
    this.value = undefined;         // Save successful data
    this.reason = undefined;        // Cause of save failure

    // +
    this.onFulfilledCallbacks = []  // Save the successful callback queue
    this.onRejectedCallbacks = []   // Save the failed callback queue

    // Execute this function in success state
    const resolve = data= > {
      if(this.status === PENDING) {
          this.status = FULFILLED;
          this.value = data;

          // +
          this.onFulfilledCallbacks.forEach(cb= >cb()); }}// Failed to execute this function
    const reject = reason= > {
      if(this.status === PENDING) {
          this.status = REJECTED;
          this.reason = reason;

          // +
          this.onRejectedCallbacks.forEach(cb= >cb()); }}try {
      // Execute executor immediately when new
      executor(resolve, reject);
    } catch(err) {
      We also throw reject for errors thrown by the executorreject(err); }}then(onFulfilled, onRejected) {
    if(this.status === FULFILLED) {
      onFulfilled(this.value);
    };
    
    if(this.status === REJECTED) {
      onRejected(this.reason);
    }

    // +
    if(this.status === PENDING) {
      this.onFulfilledCallbacks.push(() = > {
        onFulfilled(this.value);
      });
      this.onRejectedCallbacks.push(() = > {
        onRejected(this.reason); })}}}Copy the code

testDemo 3

new Promise((resolve, reject) = > {
  setTimeout(() = > {
    reject(2);
  }, 1000)
})
  .then(data= > {
    console.log('success', data);
  }, err= > {
    console.log('err', err);
  })
// After 1s, err 2 is printed
Copy the code

3. ToPromisePlus value penetration and chain calls

new Promise((resolve, reject) = > {
  resolve(3);
})
  .then()
  .then()
  .then(data= > {
    console.log('success', data);
  }, err= > {
    console.log('err', err);
  })
Copy the code

The Promise we currently implement does not support this form of invocation (value penetration). We also need to improve our Promise according to A + specifications 2.2 and 2.3. So it is recommended to look at the following implementation process for the A + specification:

test.js

// Define three states: Pending, depressing, and Rejected
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

/ / 2.3
const resolvePromise = (promise, x, resolve, reject) = > {
  2.3.1 Reject if promise and X are the same, reject because of a type error
  if (promise === x) {
    return reject(new TypeError('Chaining cycle detected for promise #<Promise>'));
  }
  2.3.3.3.3 Avoid multiple calls. Resolve /reject will not be executed after status changes
  let called = false;
  2.3.3 If x is an object/function
  if ((typeof x === 'object'&& x ! = =null) | |typeof x === 'function') {
    try {
      // 2.3.3.1 Define a then variable to save x. teng
      const then = x.then;
      // 2.3.3.3 If then is a function
      if (typeof then === 'function') {
        // 2.3.3.3 then is called with x as this. The first argument is a callback to the success state and the second argument is a callback to the failure state
        then.call(x, y= > {
          if(called) return;
          called = true;
          // 2.3.3.3.1 If the callback succeeds, proceed with resolvePromise
          resolvePromise(promise, y, resolve, reject);
        }, r= > {
          if(called) return;
          called = true;
          2.3.3.3.2 Execute reject if the callback is in the failed state
          reject(r);
        });
      } else {
        // 2.3.3.4 If then is not a function, resolve, xresolve(x); }}catch(e) {
      if(called) return;
      called = true;
      // 2.3.3.2 Reject is rejected when an error occurs when the attribute x. Chen is obtained. The error is a catch errorreject(e); }}else {
    // 2.3.4 Resolve if x is not an object/functionresolve(x); }}class Promise {
  /** * actuator *@param {Function} executor 
   */
  constructor(executor) {
    this.status = PENDING;          // The initialization is pending
    this.value = undefined;         // Save successful data
    this.reason = undefined;        // Cause of save failure
    this.onFulfilledCallbacks = []  // Save the successful callback queue
    this.onRejectedCallbacks = []   // Save the failed callback queue

    // Execute this function in success state
    const resolve = data= > {
        if(this.status === PENDING) {
            this.status = FULFILLED;
            this.value = data;
            this.onFulfilledCallbacks.forEach(cb= >cb()); }}// Failed to execute this function
    const reject = reason= > {
        if(this.status === PENDING) {
            this.status = REJECTED;
            this.reason = reason;
            this.onRejectedCallbacks.forEach(cb= >cb()); }}try {
      // Execute executor immediately when new
      executor(resolve, reject);
    } catch (err) {
      We also throw reject for errors thrown by the executorreject(err); }}/ / 2.2
  then (onFulfilled, onRejected) {
    / / value through
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value= > value;
    onRejected = typeof onRejected === 'function' ? onRejected : err= > { throw err };

    let promise = new Promise((resolve, reject) = > {
      if (this.status === FULFILLED) {
        // this is a big pity/onFulfilled/ this is the last implementation of the implementation context stack
        setTimeout(() = > {
          try {
            const x = onFulfilled(this.value);
            // 2.2.7.1 onFulfilled/onRejected (this is a pity/onFulfilled
            resolvePromise(promise, x, resolve, reject);
          } catch (e) {
            // 2.2.7.2 Ondepressing /onRejected throws error, then execute reject. The reason for the error is that the catch is caughtreject(e); }},0);
      };

      if (this.status === REJECTED) {
        // this is a big pity/onFulfilled/ this is the last implementation of the implementation context stack
        setTimeout(() = > {
          try {
            const x = onRejected(this.reason);
            // 2.2.7.1 onFulfilled/onRejected (this is a pity/onFulfilled
            resolvePromise(promise, x, resolve, reject);
          } catch (e) {
            // 2.2.7.2 Ondepressing /onRejected throws error, then execute reject. The reason for the error is that the catch is caught
            reject(e)
          }
        }, 0);
      }

      if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(() = > {
          // this is a big pity/onFulfilled/ this is the last implementation of the implementation context stack
          setTimeout(() = > {
            try {
              const x = onFulfilled(this.value);
              // 2.2.7.1 onFulfilled/onRejected (this is a pity/onFulfilled
              resolvePromise(promise, x, resolve, reject);
            } catch (e) {
              // 2.2.7.2 Ondepressing /onRejected throws error, then execute reject. The reason for the error is that the catch is caughtreject(e); }},0);
        });
        this.onRejectedCallbacks.push(() = > {
          // this is a big pity/onFulfilled/ this is the last implementation of the implementation context stack
          setTimeout(() = > {
            try {
              const x = onRejected(this.reason);
              // 2.2.7.1 onFulfilled/onRejected (this is a pity/onFulfilled
              resolvePromise(promise, x, resolve, reject);
            } catch (e) {
              // 2.2.7.2 Ondepressing /onRejected throws error, then execute reject. The reason for the error is that the catch is caught
              reject(e)
            }
          }, 0); }}}));The 2.2.7 then method must return a promise
    returnpromise; }}Copy the code

After writing, we need to verify our Promise using the test script provided by Promise A+.

  • Install the test script:npm install -g promises-aplus-tests;
  • Add the following code at the end of the test file and export it:
Promise.defer = Promise.deferred = function () {
  let dfd = {};
  dfd.promise = new Promise((resolve,reject) = >{
      dfd.resolve = resolve;
      dfd.reject = reject;
  })
  return dfd;
}

module.exports = Promise;
Copy the code
  • Finally, execute the test command:promises-aplus-tests test.js;
  • Test results:


Realize the Promise. The prototype. The catch ()

The catch method is actually implemented with the help of the THEN method, which is nothing more than a successful callback to null.

Promise.prototype.catch = function (errCallBack){
    return this.then(null, errCallBack)
}

// test
new Promise((resolve, reject) = > {
  reject(2);
})
  .then(data= > {
    console.log('success', data);
  })
  .catch(err= > {
    console.log('catch', err);    // catch 2
  })
Copy the code

Realize the Promise. The resolve ()

Promise. Resolve is to produce a successful Promise.

Promise.resolve = function(data) {
    return new Promise((resolve, reject) = >{ resolve(data); })}// test
Promise.resolve(new Promise((resolve, reject) = > {
    resolve('ok');
})).then(data= >{
  console.log(data,'success')   // ok success
}).catch(err= >{
  console.log(err,'error')})Copy the code

Promise.resolve also requires the wait function. That is, if data is a promise, it needs to complete. Rewrite constructor’s resolve method:

// Execute this function in success state
const resolve = data= > {
  // New: If data is a Promise, parse it recursively
  if (data instanceof Promise) {
    return data.then(resolve, reject);
  }
  this.status = FULFILLED;
  this.value = data;
  this.onFulfilledCallbacks.forEach(cb= > cb());
}

// test
Promise.resolve(new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('ok');
  }, 3000);
})).then(data= >{
  console.log(data,'success')   // Print after 3 seconds: OK success
}).catch(err= >{
  console.log(err,'error')})Copy the code

Thus, promise.resolve has wait capability.


Realize the Promise. Reject ()

This method returns a failed promise and simply throws the error result as reason.

Promise.reject = function(reason) {
    return new Promise((resolve, reject) = >{ reject(reason); })}// test
Promise.reject(2).then(data= >{
  console.log(data,'success')
}).catch(err= >{
  console.log(err,'error')      // 2 error
})
Copy the code

Realize the Promise. Prototype. Finally ()

This method indicates that it will be executed regardless of success/failure (not last). If the last successful result is returned, finally returns the last successful result after executing the internal code. If the last promise failed, the reason for the failure is thrown.

Promise.prototype.finally = function(callBack) {
    return this.then(data= > {
      return Promise.resolve(callBack()).then(() = > data);
    }, reason= > {
      return Promise.resolve(callBack()).then(() = > {
        throwreason; })})}// test
Promise.resolve(1).finally(() = >{
  return new Promise((resolve,reject) = >{
    setTimeout(() = > {
        resolve(2)},2000);
  })
}).then(data= >{
  console.log(data,'success')     // Prints after 2 seconds: 1 success
}).catch(err= >{
  console.log(err,'error')})Copy the code

Realize the Promise. Race ()

This method is used to process multiple requests and returns the result of which request is completed first.

Promise.race = function(promiseList) {
    if (!Array.isArray(promiseList)) {
      throw new TypeError('You must pass array')}return new Promise((resolve, reject) = > {
      for (let i = 0, len = promiseList.length; i < len; i++) {
        const val = promiseList[i];
        if (val && typeof val.then === 'function') {
          val.then(resolve, reject);
        } else {  / / common valuesresolve(val); }}})}// test
let p1 = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('ok1');
  }, 3000);
})

let p2 = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    reject('ok2');
  }, 2000);
})

Promise.race([1.2.3, p1, p2]).then(data= > {
  console.log('success', data);
}, err= > {
  console.log('error', err);
})
// success 1
Copy the code

Implement Pomise. All ()

This method is used to process multiple requests, and if multiple requests are successful, it returns an array of the results of each request’s success. If there is only one failure, there is a failure.

Promise.all = function(promiseList) {
  if (!Array.isArray(promiseList)) {
    throw new TypeError('You must pass array')}return new Promise((resolve, reject) = > {
    const resultArr = [];
    const len = promiseList.length;
    let currentIndex = 0;
    const getResult = (key, val) = > {
      resultArr[key] = val;
      if(++currentIndex === len) { resolve(resultArr); }}for (let i = 0; i < len; i++) {
      const val = promiseList[i];
      if (val && typeof val.then === 'function') {
        val.then(data= > {
          getResult(i, data);
        }, reject);
      } else {  / / common valuesgetResult(i, val); }}})}// test
let p1 = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('ok1');
  }, 1000);
})

let p2 = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('ok2');
  }, 2000);
})

Promise.all([1.2.3,p1,p2]).then(data= > {
  console.log('success', data);
}, err= > {
  console.log('error', err);
})
// Print after 2 seconds: SUCCESS [1, 2, 3, 'ok1', 'ok2']
Copy the code

Realize the Promise. AllSettled ()

The method waits until all parameter instances return a result, reject or resolve. Finally, an array is returned, each containing two attributes, status and value.

Promise.allSettled = function(promiseList) {
    if (!Array.isArray(promiseList)) {
      throw new TypeError('You must pass array')}return new Promise((resolve, reject) = > {
      const resultArr = [];
      const len = promiseList.length;
      let currentIndex = 0;
      const getResult = (key, val, status) = > {
        resultArr[key] = {
          status: status,
        };
        resultArr[key].status === 'fulfilled' ? resultArr[key].value = val : resultArr[key].reason = val;
        if(++currentIndex === len) { resolve(resultArr); }}for(let i = 0; i < len; i++) {
        const val = promiseList[i];
        if (val && typeof val.then === 'function') {
          val.then(data= > {
            getResult(i, data, 'fulfilled');
          }, reason= > {
            getResult(i, reason, 'rejected'); })}else {
          getResult(i, val, 'fulfilled'); }}})}// test
const promise1 = Promise.resolve('ok1');
const promise2 = Promise.reject('err1');
const promise3 = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    resolve('ok2');
  }, 1000);
})
const allSettledPromise = Promise.allSettled([promise1, promise2, promise3]);

allSettledPromise.then(function (results) {
  console.log(results);
});
// Output after 1 second
/* [ { status: 'fulfilled', value: 'ok1' }, { status: 'rejected', reason: 'err1' }, { status: 'fulfilled', value: 'ok2' } ] */
Copy the code

Realize the Promise. Any ()

This method is the opposite to the depressing state of Promise. All. As long as one parameter instance becomes the depressing state, the packaging instance will become the depressing state. If all parameter instances become the Rejected state, the wrapper instance becomes the Rejected state.

Promise.any = function(promiseList) {
    if (!Array.isArray(promiseList)) {
      throw new TypeError('You must pass array')}return new Promise((resolve, reject) = > {
      const resultArr = [];
      const len = promiseList.length;
      let currentIndex = 0;
      const getResult = (key, val) = > {
        resultArr[key] = val;
        if(++currentIndex === len) { reject(resultArr); }}for (let i = 0; i < len; i++) {
        const val = promiseList[i];
        if (val && typeof val.then === 'function') {
          val.then(resolve, reason= > {
            getResult(i, reason);
          });
        } else {  / / common valuesresolve(val); }}})}// test
const promise1 = Promise.reject('err1');
const promise2 = Promise.reject('err2');
const promise3 = new Promise((resolve, reject) = > {
  setTimeout(() = > {
    reject('err3');
  }, 1000);
})
const allSettledPromise = Promise.any([promise1, promise2, promise3]);

allSettledPromise
  .then(data= > {
    console.log('resolve', data);
  })
  .catch(reason= > {
    console.log('reject', reason);
  })
Reject ['err1', 'err2', 'err3']
Copy the code

At this point,ES PromiseThe methods are all implemented. Once you’ve done it yourself, yeahPromise,thenA better understanding of the chain calls and value penetration of

Refer to the link

  • ES6 – Promise use tutorial
  • Promise A + specification
  • 20 lines fulfill a Promise
  • Promise the interview questions