preface

The Promise implementation solves the problem of callback hell by writing asynchronous code synchronously. Essentially, promises are an implementation of the observer pattern that optimizes the original programming pattern through task registration and state listening, increasing the readability and maintainability of code.

Writing a Promise by hand can deepen our understanding of promises. The Promise core consists of three properties, a constructor, two instance methods, and four static methods.

Class initialization

Start by defining two properties to hold the state and result of the Promise.

class Promise {
  constructor(executor) {
    this.PromiseState = 'pending' // Store the promise state
    this.PromiseResult = null // Store promise results}}Copy the code

Executor function

When instantiating a Promise, the user needs to pass in an executor function, which receives two internal functions to change the state of the Promise, so in this step we need:

  1. Define internal functions: resolve, reject Modifies the promise state and saves the value.
  2. Call the executor function: Executor executes synchronously. If an internal exception is thrown, the promise state is Rejected.
class Promise {
  constructor(executor) {
    this.PromiseState = 'pending'; // Store the promise state
    this.PromiseResult = null; // Store promise results

    // Change the promise state to succeed
    const resolve = (data) = > {
      // If the state has changed, it will not change
      if (this.PromiseState ! = ='pending') return;
      // If the state does not change, change the state and save the value
      this.PromiseState = 'fulfilled';
      this.PromiseResult = data;
    }

    // Change the promise state to failed
    const reject = (data) = > {
      // If the state has changed, it will not change
      if (this.PromiseState ! = ='pending') return;
      // If the state does not change, change the state and save the value
      this.PromiseState = 'rejected';
      this.PromiseResult = data;
    }

    // The executor function executes synchronously
    // If an exception is thrown during execution, the promise state is failed
    try {
      executor(resolve, reject);
    } catch(error) { reject(error); }}}Copy the code

Then method

Next we need to define the then method, through which the user registers a callback function that will be executed when the Promise state changes.

  1. Then receives two callback functions as arguments. We need to check onResolved and onRejected.
  2. The then method returns a Promise and adjusts the promise state based on the onResolved, onRejected implementation.
  3. The onResolved and onRejected functions in the then method are executed asynchronously.
  4. If the state of the Promise has not changed by then execution, the callback function is saved and waits to execute, and a third property, callbacks, is added to hold the callback method registered in then.
class Promise {
  constructor(executor) {
    this.PromiseState = 'pending'; // Store the promise state
    this.PromiseResult = null; // Store promise results
    this.callbacks = []; // Save the callback method added in then

    // Change the promise state to succeed
    const resolve = (data) = > {
      // If the state has changed, it will not change
      if (this.PromiseState ! = ='pending') return;
      // If the state does not change, change the state and save the value
      this.PromiseState = 'fulfilled';
      this.PromiseResult = data;
      // Execute the callback saved in then
      this.callbacks.forEach((item) = > {
        item.onResolved();
      });
    };

    // Change the promise state to failed
    const reject = (data) = > {
      // If the state has changed, it will not change
      if (this.PromiseState ! = ='pending') return;
      // If the state does not change, change the state and save the value
      this.PromiseState = 'rejected';
      this.PromiseResult = data;
      // Execute the callback saved in then
      this.callbacks.forEach((item) = > {
        item.onRejected();
      });
    };

    // The executor function executes synchronously
    // If an exception is thrown during execution, the promise state is failed
    try {
      executor(resolve, reject);
    } catch(error) { reject(error); }}/ / then method
  then(onResolved, onRejected) {
    // check onResolved, onRejected
    if (typeofonResolved ! = ='function') {
      onResolved = value= > value
    }
    if (typeofonRejected ! = ='function') {
      onRejected = error= > {
        throw error
      }
    }
    // Then returns a Promise
    return new Promise((resolve, reject) = > {
      // Then returns the Promise state based on onResolved and onRejected
      const callback = (func) = > {
        // Catch an exception thrown when the function executes (trycatch cannot be caught at executor due to asynchronous execution)
        try {
          const result = func(this.PromiseResult);
          if (result instanceof Promise) {
            result.then(
              (value) = > {
                resolve(value);
              },
              (error) = >{ reject(error); }); }else{ resolve(result); }}catch(error) { reject(error); }};// If the PROMISE state has changed, the callback is executed asynchronously
      // In real life, callbacks are added to the microtask queue, which is simulated by macro tasks
      if (this.PromiseState === 'fulfilled') {
        setTimeout(() = > {
          callback(onResolved);
        });
      }
      if (this.PromiseState === 'rejected') {
        setTimeout(() = > {
          callback(onRejected);
        });
      }
      // If the promise state has not changed, save it first
      These methods are executed when the resolve or reject methods are executed in the executor function
      if (this.PromiseState === 'pending') {
        this.callbacks.push({
          onResolved: () = > {
            setTimeout(() = > {
              callback(onResolved);
            });
          },
          onRejected: () = > {
            setTimeout(() = >{ callback(onRejected); }); }}); }}); }}Copy the code

Catch method

The catch method is the syntactic sugar for the then method, so let’s define it.

  // The catch method is defined inside the Promise class.
  catch(onRejected) {
    return this.then(undefined, onRejected);
  }
Copy the code

Resolve method

Next we define several static methods, starting with resolve, which wraps the data as a Promise and returns it if the data passed in is a Promise, otherwise returning a Promise that takes the input as the success state of the result

  / / resolve method
  static resolve(data) {
    // If a Promise is passed in, it is returned directly
    if (data instanceof Promise) {
      return data;
    } else {
      return new Promise((resolve) = >{ resolve(data); }); }}Copy the code

Reject method

Reject, which returns a failed Promise regardless of whether the input is a Promise or not, and the value is the incoming data

  / / reject method
  static reject(data) {
    return new Promise((_, reject) = > {
      reject(data);
    });
  }
Copy the code

All methods

The all method is used to determine whether multiple promises are successful. If multiple promises are successful, an array of returned values for all incoming promises is returned. If one promise fails, the return value of that failed promise is returned

  / / all methods
  static all(promises) {
    let count = 0;
    let arr = [];
    return new Promise((resolve, reject) = > {
      for (let i = 0; i < promises.length; i++) {
        promises[i].then(
          (value) = > {
            count++;
            arr[i] = value;
            if(count === promises.length) { resolve(arr); }},(error) = >{ reject(error); }); }}); }Copy the code

Race method

Finally, race, which is used for multiple promise competing implementations, returns the return value of that successful promise if one of them succeeds; If one of the promises fails, the return value of that failed promise is returned

  / / race method
  static race(promises) {
    return new Promise((resolve, reject) = > {
      for (let i = 0; i < promises.length; i++) {
        promises[i].then(
          (value) = > {
            resolve(value);
          },
          (error) = >{ reject(error); }); }}); }Copy the code