Using Promises solves callback hell, multiple asynchronous requests, and more. So how does it work? Let’s make it happen

Implementation of synchronous invocation

First, we need to know:

  • Promise is a class
  • A new Promise returns a Promise object that passes an executor that executes immediately
  • In addition, each promise instance has a THEN method that takes two methods: success (with a success value) and failure (with a failure origin)
  • Promise has three states: a success state, a failure state, and a wait state.
  • The default state is a wait state, which can change to a success state or a failure state
  • Once you succeed or fail you can’t go into any other state and you know that, you can implement it very simply
Class Promise {constructor(executor) {// Executor executor this.status ='pending'; // Default wait state this.value = undefined; // Success value this.reason = undefined // failurelet resolve = (value) => {
      if (this.status === 'pending') {
        this.status = 'resolved'; // success this.value = value; }}let reject = (reason) => {
      if (this.status === 'pending') {
        this.status = 'rejected'; // fail this. Reason = reason; } } executor(resolve, reject); // By default the upper executor executes}then(onFufilled, onRejected) {
    if (this.status === 'resolved') {// Successful state onFufilled(this.value); }if (this.status === 'rejected') {// Reject (this.reason); } } } module.exports = PromiseCopy the code

Above, we simply implemented a synchronous promise. Test it out

let Promise = require('./myPromise.js')
let promise = new Promise((resolve, reject) => {
    resolve('hello')
})
promise.then((data) => {
    console.log(data)
}, (err) => {
    console.log(err)
})
Copy the code

Print result:

However, as we know, Promise primarily addresses the issue of asynchronous callbacks. Therefore, asynchronous invocation must be implemented.

Implementation of asynchronous invocation

In the case of asynchronous invocation, when the then of the instance is called, the state may still be pending. In this case, we need to define two arrays of successful and failed methods on the instance, and put the methods that need to be executed into the corresponding arrays respectively. When the asynchronous time is up, we can execute the methods in the corresponding array.

Class Promise {constructor(executor) {// Executor executor this.status ='pending'; // Default wait state this.value = undefined; // Success value this.reason = undefined // failure original + // storethenArray of successful, failed callbacks this.onResovlecallbacks = []; this.onRejectedCallbacks = [];let resolve = (value) => {
      if (this.status === 'pending') {
        this.status = 'resolved'; // success this.value = value; + this.onResovleCallbacks.forEach(fn => fn()); }}let reject = (reason) => {
      if (this.status === 'pending') {
        this.status = 'rejected'; // fail this. Reason = reason; + this.onRejectedCallbacks.forEach(fn => fn()); } } executor(resolve, reject); // By default the upper executor executes}then(onFufilled, onRejected) {
    if (this.status === 'resolved') {// Successful state onFufilled(this.value); }if (this.status === 'rejected') {// Reject (this.reason); } +if (this.status === 'pending') {
      this.onResovleCallbacks.push(() => {
        onFufilled(this.value)
      });
      this.onRejectedCallbacks.push(() => {
        onRejected(this.reason)
      })
    }
  }
}

module.exports = Promise
Copy the code

At this point, we’ve implemented the asynchronous invocation of Promise. Test it out

let Promise = require('./myPromise.js')
let promise = new Promise((resolve, reject) => {
    setTimeout(function(){
        resolve('hello')
    },100)
})
promise.then((data) => {
    console.log(data)
}, (err) => {
    console.log(err)
})
Copy the code

Print result:

Abnormal execution processing

When an exception is thrown, we should have it execute the error method of THEN when the state changes to Rejected. Error check (rejected); error check (rejected)

try { executor(resolve, reject); } catch (e) {reject(e) {reject(e); }Copy the code

Test it out

let Promise = require('./myPromise.js')
let promise = new Promise((resolve, reject) => {
   throw new Error('❌)
})
promise.then((data) => {
    console.log(data)
}, (err) => {
    console.log(err)
})
Copy the code

Print result:

Implementation of chain calls

When it comes to chained calls, the most common thing we touch on is jquery. Jquery implements chained calls by returning this. Does promise implement chained calls that return this? The answer is, NO! It implements chained calls by returning a new promise. In the THEN method, no matter what state a promise is in, a new promise is returned after execution.

 then(onFufilled, onRejected) {
  +  letpromise2; // return new promise + promise2 = new promise ((resolve, reject) => {if (this.status === 'resolved') {
        onFufilled(this.value);
      }
      if (this.status === 'rejected') { 
        onRejected(this.reason);
      }
      if (this.status === 'pending') {
        this.onResovleCallbacks.push(() => {
          onFufilled(this.value)
        });
        this.onRejectedCallbacks.push(() => {
          onRejected(this.reason)
        })
      }
    });
  +  return promise2;
  }
Copy the code

Error cases for chain calls

In THEN, either a successful callback or a failed callback returns the successful callback in the next THEN, or the failed callback in the next THEN if there is an error. That is, the state of the next THEN is independent of the state in which the previous THEN is executed. If onFufilled, onRejected fails, then onFufilled, onRejected fails

 promise2 = new Promise((resolve, reject) => {
      if (this.status === 'resolved') {
        try {
          onFufilled(this.value);
        } catch (e) {
          reject(e)
        }
      }
      if (this.status === 'rejected') {
        try {
          onRejected(this.reason);
        } catch (e) {
          reject(e)
        }
      }
      if (this.status === 'pending') { this.onResovleCallbacks.push(() => { try { onFufilled(this.value) } catch (e) { reject(e); }}); this.onRejectedCallbacks.push(() => { try { onRejected(this.reason) } catch (e) { reject(e); }}}}));Copy the code

Test it out

let promise = new Promise((resolve, reject) => {
    resolve('hello')
})
promise.then((data) => {
    console.log(data)
    throw new Error('🙅')
}, (err) => {
    console.log(err)
}).then((data) => {
    console.log(data)
}, (err) => {
    console.log('🙅' + err)
})
Copy the code

Print result:

Chain calls are compatible with a variety of situations

  • If the first Promise returns a normal value, pass the return value directly to resolve for the next THEN.
  • If the first promise returns a promise, then each state of the then method needs to be handled:
Try {//x is the last promise return value, which could be a normal value or a promise; X could also be a promise from someone else, so we could write a method, and we could handle it all togetherletx=onFufilled(this.value); // Add parameter: next timethenInstance of promise2, the return value x, promise2 successful method, resolvePromise promise2 failure method (promise2, x, resolve, reject); } catch (e) { reject(e) }Copy the code

ResolvePromise: resolvePromise: resolvePromise: resolvePromise: resolvePromise

/* * resolvePromise * @parameters * promise2: next timethenPromise2 * x: returns x * resolve: successful method of promise2 * reject: failed method of promise2 */functionResolvePromise (promise2, x, resolve, reject) {//x may be someone else's promise, so allow others to write as much as possibleif(promise2 === x) {// If it returns the same result as the promise, it will never succeedreturn reject(new TypeError('Circular reference'));
      }
      letcalled; // See if x is a promise. A promise should be an objectif(x ! = null && (typeof x ==='object' || typeof x === 'function'// Promise try {let then= x.then; // If it is an object, try fetching itthenMethod if there arethen"Thought it was promiseif (typeof then= = ='function') {// IfthenCall (x, y => {// Only one call can be made on success or failureif (called) return;
              called = true; ResolvePromise (promise2, y, resolve, reject); }, r => {if (called) return;
              called = true; reject(r); // Fail to fail})}else{ resolve(x); }} catch (e) {//thenIf something goes wrong, don't continueif (called) return;
          called = true; reject(e); }}elseResolve (x) {// promise2 becomes resolve(x); }};Copy the code

Test it out

  • Returns a normal value
let promise = new Promise((resolve, reject) => {
    resolve('hello')
})
promise.then((data) => {
    console.log(data)
    throw new Error('🙅')
}, (err) => {
    console.log(err)
}).then((data) => {
    console.log(data)
}, (err) => {
    console.log('🙅' + err)
})
Copy the code

Print result:

  • Return a Promise
let promise = new Promise((resolve, reject) => {
    resolve('hello')
})
promise.then((data) => {
    console.log(data)
    return new Promise((resolve, reject) => {
        resolve('👋')
    })
}, (err) => {
    console.log(err)
}).then((data) => {
    console.log(data)
}, (err) => {
    console.log('🙅' + err)
})
Copy the code

Print result:

Above, our promise seems to be almost there, but there is still a problem to deal with. Hen source code can be implemented in the implementation of nothing. Promise calls this the penetration of values. Therefore, we need to fault-tolerant the then method’s input parameters in the THEN method:

onFufilled = typeof onFufilled === 'function' ? onFufilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err};
Copy the code

Test it out

let Promise = require('./myPromise.js')
let promise = new Promise((resolve, reject) => {
    resolve('hello')
})
promise.then().then((data) => {
    console.log(data)
}, (err) => {
    console.log('🙅' + err)
})
Copy the code

Print result:

In addition, the Promise specification states that all onFufilled and onRejected must be executed asynchronously. This may cause instability in the test if the onFufilled and onRejected methods are not added asynchronously.

 if (this.status === 'resolved') {
        setTimeout(() => {
          try {
            let x=onFufilled(this.value);
            resolvePromise(promise2,x,resolve,reject);
          } catch (e) {
            reject(e)
          }
        }, 0);
      }
Copy the code

Promise to realize

Class Promise {constructor(executor) {// Executor executor this.status ='pending'; // Default wait state this.value = undefined; // This. Reason = undefined // this. OnResovleCallbacks = []; this.onRejectedCallbacks = [];let resolve = (value) => {
      if (this.status === 'pending') {
        this.status = 'resolved'; // success this.value = value; this.onResovleCallbacks.forEach(fn => fn()); }}let reject = (reason) => {
      if (this.status === 'pending') {
        this.status = 'rejected'; // fail this. Reason = reason; this.onRejectedCallbacks.forEach(fn => fn()); } } try { executor(resolve, reject); } catch (e) {reject(e) {reject(e); }}then(onFufilled, onRejected) {
    onFufilled = typeof onFufilled === 'function' ? onFufilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : err => {
      throw err
    };
    functionResolvePromise (promise2, x, resolve, reject) {//x may be someone else's promise, so allow others to write as much as possibleif(promise2 === x) {// If it returns the same result as the promise, it will never succeedreturn reject(new TypeError('Circular reference')); } / /letcalled; // see if x is a promise. A promise should be an objectif(x ! = null && (typeof x ==='object' || typeof x === 'function'// Promise try {let then= x.then; // If it is an object, I will try to fetch itthenMethod if there arethen"Thought it was promiseif (typeof then= = ='function'{/ /thenCall (x, y => {// Only one call can be made on success or failureif (called) return;
              called = true; ResolvePromise (promise2, y, resolve, reject); }, r => {if (called) return;
              called = true; reject(r); // Fail to fail})}else{ resolve(x); }} catch (e) {//thenIf something goes wrong, don't continueif (called) return;
          called = true; reject(e); }}elseResolve (x) {// promise2 becomes resolve(x); }};letpromise2; Promise2 = new promise ((resolve, reject) => {if (this.status === 'resolved') {
        setTimeout(() => {
          try {
            letx = onFufilled(this.value); //x is the last promise return value, which could be a normal value or a promise; X can also be a promise of someone else. We can write a method that handles resolvepromises (promise2, x, resolve, reject) uniformly. / / the nextthen} catch (e) {reject(e)}}, 0); }if (this.status === 'rejected') {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e)
          }
        }, 0)
      }
      if (this.status === 'pending') {
        this.onResovleCallbacks.push(() => {
          setTimeout(() => {
            try {
              letx = onFufilled(this.value) resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }}}, 0)); this.onRejectedCallbacks.push(() => {setTimeout(() => {
            try {
              letx = onRejected(this.reason) resolvePromise(promise2, x, resolve, reject); } catch (e) { reject(e); }}, 0)})}});return promise2;
  }
}
module.exports = Promise
Copy the code

test

Above, we basically completed a promise library of our own. Finally, to see if the library works, you need to test it. There is a library called Promises – aplus-Tests that will help us verify whether this library is feasible. In addition, the test needs to defer, which is the syntactic sugar of Promise.

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

The installation

npm install -g promises-aplus-tests
Copy the code

perform

promises-aplus-tests ./myPromise.js 
Copy the code

Above, we completed A Promise based on the Promise A+ specification by ourselves.