This article will take you through a step-by-step detailed analysis, starting from zero, write promise source code.

The core logical implementation of the Promise class

First, let’s take a look at what promises look like when they finally come true

let promise = new Promise((resolve, reject) = > {
  resolve('success');
  reject('failure');
});
promise.then(
  (value) = > {
    console.log(value)
  },
  (reason) = > {
    console.log(reason)
  }
);
Copy the code

Through the code, we can see that the promise uses the new keyword. The constructor passes in a function that takes resolve and reject.

Based on the above analysis, we can write the following code

class MyPromise {  // Name the class MyPromise
  constructor(executor){  // The constructor passes in a function, executor, which executes immediately
    executor(resolve,reject)
  }
}
Copy the code

We also know that resolve and reject in promises are used to implement state state transitions. Resolve converts the wait state to a success state, reject converts the wait state to a failure state. That is, there will be a state quantity in the promise, which will be converted from wait -> success/failure through the class methods in the promise. So improve the code:

// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = 'pending'
const FULFILLED = 'fulfilled' 
const REJECT = 'reject'

class MyPromise { // Class naming conventions
  constructor(executor){
    executor(this.resolve,this.reject)
  }
  status = PENDING // State variables
  
  resolve(){ // State transition on success
    this.status = FULFILLED
  }
  reject(){ // State transition on failure
    this.status = REJECT
  }
}
Copy the code

At the same time, we know that state transitions can only be made by waiting to transition to success or failure. That is, we do not perform state transitions when the initial state is not waiting. The above code is optimized to become

// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = 'pending'
const FULFILLED = 'fulfilled' 
const REJECT = 'reject'

class MyPromise { // Class naming conventions
  constructor(executor){
    executor(this.resolve,this.reject)
  }
  status = PENDING // State variables
  
  resolve(){ // State transition on success
    if(this.status ! == PENDING)return // The transition is not performed in the wait state
    this.status = FULFILLED
  }
  reject(){ // State transition on failure
    if(this.status ! == PENDING)return // The transition is not performed in the wait state
    this.status = REJECT
  }
}
Copy the code

In the use of promise, then is used to handle callbacks, promise.then(). Two functions are passed to represent successful and failed callbacks. Successful callback functions take value and failed functions take Reason, completing the Promise class as follows

// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = 'pending'
const FULFILLED = 'fulfilled' 
const REJECT = 'reject'

class MyPromise { // Class naming conventions
  constructor(executor){
    executor(this.resolve,this.reject)
  }
  status = PENDING // State variables
  
  resolve = () = >{ // State transition on success
    if(this.status ! == PENDING)return
    this.status = FULFILLED
  }
  reject= () = >{ // State transition on failure
    if(this.status ! == PENDING)return
    this.status = REJECT
  }
  then(successCallback,failCallback){  // Then passes two callback functions, one successful and one failed
      if(this.status === FULFILLED){ 
        successCallback(value)        // This is a big pity
      }else if(this.status === REJECT){
        failCallback(reason)         // The status is REJECT callback}}}Copy the code

We also know that the value of successCallback and reason in failCallback are the arguments passed in when the state transition is performed. We define value and Reason as one variable.

// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = 'pending'
const FULFILLED = 'fulfilled' 
const REJECT = 'reject'

class MyPromise { // Class naming conventions
  constructor(executor){
    executor(this.resolve,this.reject)
  }

  status = PENDING // State variables
  value = undefined // Success value
  reason = undefined // Cause of failure

  resolve=value= >{ // State transition on success
    if(this.status ! == PENDING)return
    this.status = FULFILLED
    this.value = value // Save the amount of success
  }
  reject=reason= >{ // State transition on failure
    if(this.status ! == PENDING)return
    this.status = REJECT
    this.reason = reason // Save the failure cause after the failure
  }
  then(successCallback,failCallback){  // Then passes two callback functions, one successful and one failed
      if(this.status === FULFILLED){ 
        successCallback(this.value)        // This is a big pity
      }else if(this.status === REJECT){
        failCallback(this.reason)         // The status is REJECT callback}}}module.exports = MyPromise;
Copy the code

With that said, the core logic of promise has been implemented, and now let’s test the code

const MyPromise = require("./myPromise"); / / import

let promise = new MyPromise((resolve, reject) = > {
  resolve("Success"); // Implement a successful function
  // reject(" reject ");
});

promise.then(
  (value) = > {
    console.log(value);
  },
  (reason) = > {
    console.log(reason); });Copy the code

At this point, our entire promise core logic is completed.

Add asynchronous logic to the Promise class

The MyPromise class we implemented above does not consider the implementation of asynchronous code. We put asynchronous logic in the middle of the test code.

const MyPromise = require("./myPromise");

let promise = new MyPromise((resolve, reject) = > {
  // resolve(" resolve ");
  // reject(" reject ");
  setTimeout(() = > { // The state transition is executed after 2 seconds. The then method is still PANDING, but there is no logic for processing PENDING state in then.
    resolve("Success");
  }, 2000);
});

promise.then(
  (value) = > {
    console.log(value);
  },
  (reason) = > {
    console.log(reason); });Copy the code

Since we do not implement PENDING logic in the THEN method, we implement it now. Since the state transition has not been implemented while waiting for the state, we store the two callback functions first. The state transition functions resolve and reject are then called to ensure that the callback is fired.

// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";

class MyPromise {
  // Class naming conventions
  constructor(executor) {
    executor(this.resolve, this.reject);
  }

  status = PENDING; // State variables
  value = undefined; // Success value
  reason = undefined; // Cause of failure
  successCallback = undefined; // Success callback function
  failCallback = undefined; // Failed callback function

  resolve = (value) = > {
    // State transition on success
    if (this.status ! == PENDING)return;
    this.status = FULFILLED;
    this.value = value;
    this.successCallback && this.successCallback(value); // Check whether the successful callback function exists
  };
  reject = (reason) = > {
    // State transition on failure
    if (this.status ! == PENDING)return;
    this.status = REJECT;
    this.reason = reason;
    this.failCallback && this.failCallback(reason); // Check whether the failed callback function exists
  };
  then(successCallback, failCallback) {
    // Then passes two callback functions, one successful and one failed
    if (this.status === FULFILLED) {
      successCallback(this.value); // This is a big pity
    } else if (this.status === REJECT) {
      failCallback(this.reason); // The status is REJECT callback
    } else {
      // Wait state, let's first store the callback function
      this.successCallback = successCallback;
      this.failCallback = failCallback; }}}module.exports = MyPromise;
Copy the code

Using the asynchronous test code above again, the test found perfect execution, and now we have implemented the asynchronous logic in the Promise class.

Implement multiple calls to THEN

We know that promise’s then method can be called multiple times, but the callback function in our code above can only store one function, so we need to modify it to implement multiple calls to THEN

// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";

class MyPromise {
  // Class naming conventions
  constructor(executor) {
    executor(this.resolve, this.reject);
  }

  status = PENDING; // State variables
  value = undefined; // Success value
  reason = undefined; // Cause of failure
  successCallback = []; // Success callback function
  failCallback = []; // Failed callback function

  resolve = (value) = > {
    // State transition on success
    if (this.status ! == PENDING)return;
    this.status = FULFILLED;
    this.value = value;
    // this.successCallback && this.successCallback(value);
    // console.log(this.successCallback);
    while (this.successCallback.length) // Loop to determine if there are any callback functions in the success array that have not yet been executed. If there are any callback functions in the success array, exit the queue and execute the function
      this.successCallback.shift()(this.value);
  };
  reject = (reason) = > {
    // State transition on failure
    if (this.status ! == PENDING)return;
    this.status = REJECT;
    this.reason = reason;
    // this.failCallback && this.failCallback(reason);
    while (this.failCallback.length) this.failCallback.shift()(this.reason);// Check if there are any callback functions in the failed array that have not yet been executed
  };
  then(successCallback, failCallback) {
    // Then passes two callback functions, one successful and one failed
    if (this.status === FULFILLED) {
      successCallback(this.value); // This is a big pity
    } else if (this.status === REJECT) {
      failCallback(this.reason); // The status is REJECT callback
    } else {
      // Wait state, let's first store the callback function
      this.successCallback.push(successCallback);
      this.failCallback.push(failCallback); }}}module.exports = MyPromise;
Copy the code

So far, we have implemented multiple calls to THEN.

Chain calls to THEN

We know that promise can be invoked in a Xi ‘an chain, but the code above doesn’t do that. And let’s change the test code

const MyPromise = require("./myPromise");

let promise = new MyPromise((resolve, reject) = > {
  // resolve(" resolve ");
  // reject(" reject ");
  setTimeout(() = > {
    // The state transition is executed after 2 seconds. The then method is still PANDING, but there is no logic for processing PENDING state in then.
    resolve("Success");
  }, 2000);
});

promise //promise
  .then(
    (value) = > {
      console.log(value);
    },
    (reason) = > {
      console.log(reason);
    }
  )
  .then((value) = > {
    console.log(value);
  });

Copy the code

And we looked at chained calls and we saw that in order to do chained calls we have to return a promise object in each THEN because only promise objects can call then methods, and we also have to pass values between chained THEN’s. Next class, we’re going to implement a chain call to THEN

Let’s write the test code first

const MyPromise = require("./myPromise");

let promise = new MyPromise((resolve, reject) = > {
  resolve("Success");
  // reject(" reject ");
  // setTimeout(() => {
  // // : the state transition will take two seconds. The code executes the then method first, but the state is still PANDING, but there is no logic for processing PENDING state in then, so we add it.
  // resolve("hahha1");
  // }, 2000);
});

promise
  .then((value) = > {
    // console.log(123);
    console.log(value);
    return 100;
  })
  .then((value) = > {
    console.log(value);
  });
Copy the code

Then modify the code as follows

// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";

class MyPromise {
  // Class naming conventions
  constructor(executor) {
    executor(this.resolve, this.reject);
  }

  status = PENDING; // State variables
  value = undefined; // Success value
  reason = undefined; // Cause of failure
  successCallback = []; // Success callback function
  failCallback = []; // Failed callback function

  resolve = (value) = > {
    // State transition on success
    if (this.status ! == PENDING)return;
    this.status = FULFILLED;
    this.value = value;
    // this.successCallback && this.successCallback(value);
    // console.log(this.successCallback);
    while (this.successCallback.length)
      this.successCallback.shift()(this.value);
  };
  reject = (reason) = > {
    // State transition on failure
    if (this.status ! == PENDING)return;
    this.status = REJECT;
    this.reason = reason;
    // this.failCallback && this.failCallback(reason);
    while (this.failCallback.length) this.failCallback.shift()(this.reason);
  };
  then(successCallback, failCallback) {
    let promise2 = new MyPromise((resolve, reject) = > { Define a new Promise object
      // Then passes two callback functions, a successful callback and a failed callback. Since the function passed to the constructor is executed, other statements can be put in it
      if (this.status === FULFILLED) {
        let x = successCallback(this.value); // This is a big pity
        resolve(x); This is where the implementation passes the return value of the previous Promise object to the next promise
      } else if (this.status === REJECT) {
        failCallback(this.reason); // The status is REJECT callback
      } else {
        // Wait state, let's first store the callback function
        this.successCallback.push(successCallback);
        this.failCallback.push(failCallback); }});return promise2; // Return this Promise object}}module.exports = MyPromise;
Copy the code

So now we have a chain call to the then method. But sometimes our then returns a promise object in addition to a value. That is, we now need to determine whether the returned x is a normal value or a Promise object. If it is a normal value, we call resolve directly. If it is a Promise object, we look at the result that the promise object returns, and decide whether the call returns resolve or reject.

Let’s write the test code first

const MyPromise = require("./myPromise");

let promise = new MyPromise((resolve, reject) = > {
  resolve("Success");
  // reject(" reject ");
  // setTimeout(() => {
  // // : the state transition will take two seconds. The code executes the then method first, but the state is still PANDING, but there is no logic for processing PENDING state in then, so we add it.
  // resolve("hahha1");
  // }, 2000);
});

promise
  .then((value) = > {
    // console.log(123);
    console.log(value);
    return other();
  })
  .then((value) = > {
    console.log(value);
  });

function other(){
    return new Promise((resolve,reject) = >{
        resolve('successful 123')})}Copy the code

The next improvement to our original code is as follows

// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";

class MyPromise {
  // Class naming conventions
  constructor(executor) {
    executor(this.resolve, this.reject);
  }

  status = PENDING; // State variables
  value = undefined; // Success value
  reason = undefined; // Cause of failure
  successCallback = []; // Success callback function
  failCallback = []; // Failed callback function

  resolve = (value) = > {
    // State transition on success
    if (this.status ! == PENDING)return;
    this.status = FULFILLED;
    this.value = value;
    // this.successCallback && this.successCallback(value);
    // console.log(this.successCallback);
    while (this.successCallback.length)
      this.successCallback.shift()(this.value);
  };
  reject = (reason) = > {
    // State transition on failure
    if (this.status ! == PENDING)return;
    this.status = REJECT;
    this.reason = reason;
    // this.failCallback && this.failCallback(reason);
    while (this.failCallback.length) this.failCallback.shift()(this.reason);
  };
  then(successCallback, failCallback) {
    let promise2 = new MyPromise((resolve, reject) = > {
      // Then passes two callback functions, one successful and one failed
      if (this.status === FULFILLED) {
        let x = successCallback(this.value); // This is a big pity
        resolvePromise(x, resolve, reject); // Call resolvePromise
      } else if (this.status === REJECT) {
        failCallback(this.reason); // The status is REJECT callback
      } else {
        // Wait state, let's first store the callback function
        this.successCallback.push(successCallback);
        this.failCallback.push(failCallback); }});returnpromise2; }}If x is a Promise, then resolve (reject) is called
function resolvePromise(x, resolve, reject) {
  if (x instanceof Promise) {
    //x is the promise object
    x.then(resolve, reject);
  } else {
    //x is normalresolve(x); }}module.exports = MyPromise;
Copy the code

We are now ready to implement processing of objects that return a promise value. The problem is that if the promise object itself is returned in the then method, the code above will loop, which is not allowed. In the system’s promise, an error occurs in this case, but no error occurs.

Loop calls:

const MyPromise = require("./myPromise");

let promise = new MyPromise((resolve, reject) => {
  resolve("Success");
  // reject(" reject ");
  // setTimeout(() => {
  // // : the state transition will take two seconds. The code executes the then method first, but the state is still PANDING, but there is no logic for processing PENDING state in then, so we add it.
  // resolve("hahha1");
  // }, 2000);
});

let p1 = promise.then((value) => {  // There is a loop call for promise
  // console.log(123);
  console.log(value);
  return p1; // Returns the invoked promise itself}); p1.then( (value) => { console.log(value); }, (reason) => { console.log(reason); });function other(a) {
  return new Promise((resolve, reject) => {
    resolve("L" success);
  });
}
Copy the code

In the resolvePromise, the x is the same as the promise that invokes it. If so, do not proceed to execute the code. The improved code looks like this

// The three states (wait, success, and failure) are defined as constants for later reuse and code prompting
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECT = "reject";

class MyPromise {
  // Class naming conventions
  constructor(executor) {
    executor(this.resolve, this.reject);
  }

  status = PENDING; // State variables
  value = undefined; // Success value
  reason = undefined; // Cause of failure
  successCallback = []; // Success callback function
  failCallback = []; // Failed callback function

  resolve = (value) = > {
    // State transition on success
    if (this.status ! == PENDING)return;
    this.status = FULFILLED;
    this.value = value;
    // this.successCallback && this.successCallback(value);
    // console.log(this.successCallback);
    while (this.successCallback.length)
      this.successCallback.shift()(this.value);
  };
  reject = (reason) = > {
    // State transition on failure
    if (this.status ! == PENDING)return;
    this.status = REJECT;
    this.reason = reason;
    // this.failCallback && this.failCallback(reason);
    while (this.failCallback.length) this.failCallback.shift()(this.reason);
    // while (this.failCallback.length) console.log(this.failCallback);
  };
  then(successCallback, failCallback) {
    let promise2 = new MyPromise((resolve, reject) = > {
      // Then passes two callback functions, one successful and one failed
      if (this.status === FULFILLED) {
        setTimeout(() = > {  ResolvePromise () {resolvePromise () {resolvePromise () {resolvePromise () {resolvePromise (); So that's how you get promise2
          let x = successCallback(this.value); // This is a big pity
          resolvePromise(promise2, x, resolve, reject);
        }, 0);
      } else if (this.status === REJECT) {
        failCallback(this.reason); // The status is REJECT callback
      } else {
        // Wait state, let's first store the callback function
        this.successCallback.push(successCallback);
        this.failCallback.push(failCallback); }});returnpromise2; }}// Determine if x is a promise object, if resolve, if reject
function resolvePromise(promise2, x, resolve, reject) {
  if (promise2 === x) {
    return reject( // If the call is circular, a type error is reported
      new TypeError("Chaining cycle detected for promise #<Promise>")); }if (x instanceof Promise) {
    //x is the promise object
    x.then(resolve, reject);
  } else {
    //x is normalresolve(x); }}module.exports = MyPromise;
Copy the code

Improve code to increase robustness

Actuator error

So let’s remodel it

Some of my insights are:

Start with the end to analyze the problem, by the use of promise to reverse the derivation. Then use the pyramid principle step by step.

From the beginning of mastering the use of tools, human beings are accelerating progress, mastering tools is a good way to achieve