In modern JavaScript, Promises are an important tool for handling asynchronous operations. By handwriting a simple Promise class, we can have a deeper understanding of the working principle of Promise and the mechanism of asynchronous programming. In this blog, we will gradually build a fully functional Promise class, including basic structure, thenmethods, static methods such as resolverejectallallSettledraceand any, as well as some auxiliary methods.

Basic structure of Promise

First, we define a MyPromiseclass that contains common Promise states and basic structures:

class MyPromise {
  static REJECTED = 'rejected';
  static PENDING = 'pending';
  static FULFILLED = 'fulfilled';

  value = undefined;
  status = MyPromise.PENDING;
  onFulfilledCallBacks = [];
  onRejectedCallBacks = [];

  constructor(execute) {
    const resolve = (value) => {
      if (this.status === MyPromise.PENDING) {
        this.value = value;
        this.status = MyPromise.FULFILLED;
        this.onFulfilledCallBacks.forEach((func) => func(value));
      }
    };

    const reject = (value) => {
      if (this.status === MyPromise.PENDING) {
        this.value = value;
        this.status = MyPromise.REJECTED;
        this.onRejectedCallBacks.forEach((func) => func(value));
      }
    };

    try {
      execute(resolve, reject);
    } catch (error) {
      reject(error);
    }
  }
  // ...()
}

The basic structure of the class is defined here MyPromise, including state constants, initial values, status and execution callback arrays, etc.

Implementationthen

thenMethods are the core of Promise and are used to register callback functions that are executed when the Promise is completed. Here is thenthe basic implementation of the method:

then(onFulfilled, onRejected) {
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (onFulfilled) => onFulfilled;
  onRejected = typeof onRejected === 'function' ? onRejected : (onRejected) => onRejected;

  return new MyPromise((resolve, reject) => {
    // 
    if (this.status === MyPromise.FULFILLED) {
      try {
        queueMicrotask(() => {
          const result = onFulfilled(this.value);
          this.handlePromiseResult(result, resolve, reject);
        });
      } catch (error) {
        reject(error);
      }
    }
    // 
    else if (this.status === MyPromise.REJECTED) {
      try {
        queueMicrotask(() => {
          const result = onRejected(this.value);
          this.handlePromiseResult(result, resolve, reject);
        });
      } catch (error) {
        reject(error);
      }
    }
    // 
    else {
      this.onFulfilledCallBacks.push((value) => {
        queueMicrotask(() => {
          const result = onFulfilled(value);
          this.handlePromiseResult(result, resolve, reject);
        });
      });
      this.onRejectedCallBacks.push((value) => {
        queueMicrotask(() => {
          const result = onRejected(value);
          this.handlePromiseResult(result, resolve, reject);
        });
      });
    }
  });
}

handlePromiseResult(result, resolve, reject) {
  if (result instanceof MyPromise) {
    result.then(resolve, reject);
  } else {
    resolve(result);
  }
}

In thenthe method, we determine the current Promise state and execute the corresponding callback function according to the different states. If it is an asynchronous state, push the callback function into the corresponding callback array and wait for execution when the state changes.

Implementation resolveand rejectstatic methods

resolveand rejectare MyPromisetwo static methods of the class that are used to create resolved or rejected Promises. Here are their implementations:

static resolve = (value) => {
  return new MyPromise((resolve, reject) => {
    resolve(value);
  });
};

static reject = (value) => {
  return new MyPromise((resolve, reject) => {
    reject(value);
  });
};

These two methods return a resolved or rejected Promise respectively.

Reality

Present allmethod

allThe method receives an array of Promise and returns a new Promise. Only when all input Promises resolve, the new Promise will be resolved and an array containing the results of all Promises will be returned; otherwise, the new Promise will be rejected as long as one Promise rejects.

static all = (promises) => {
  if (!this._hasIterator(promises)) {
    throw new Error('Parameters are not iterable');
  }

  return new MyPromise((resolve, reject) => {
    const resultArr = [];
    promises.forEach((promise) => {
      promise.then(
        (res) => {
          if (resultArr.length === promises.length) {
            resolve(resultArr);
          }
          resultArr.push(res);
        },
        (err) => {
          reject(err);
        }
      );
    });
  });
};

Implementation allSettledraceand anymethods

allSettledMethod returns a Promise that resolves after all given Promises have resolved or been rejected. It waits for all Promises to complete, whether successful or failed, and returns an array containing the results of each Promise.

static allSettled = (promises) => {
  if (!this._hasIterator(promises)) {
    throw new Error('Parameters are not iterable');
  }

  return new MyPromise((resolve) => {
    const resultArr = [];
    promises.forEach((promise, index) => {
      promise.then(
        (res) => {
          resultArr.push({ status: 'fulfilled', value: res });
          if (resultArr.length === promises.length) {
            resolve(resultArr);
          }
        },
        (res) => {
          resultArr.push({ status: 'rejected', reason: res });
          if (resultArr.length === promises.length) {
            resolve(resultArr);
          }
        }
      );
    });
  });
};

raceMethod returns a Promise that resolves immediately after any given Promise resolves or rejects.

static race = (promises) => {
  if (!this._hasIterator(promises)) {
    throw new Error('Parameters are not iterable');
  }

  return new MyPromise((resolve, reject) => {
    promises.forEach((promise) => {
      promise.then(
        (res) => {
          resolve(res);
        },
        (res) => {
          reject(res);
        }
      );
    });
  });
};

anyMethod returns a Promise that resolves immediately after any given Promise resolves. The difference raceis anythat only one Promise can be resolved, regardless of whether its status is success or failure.

static any = (promises) => {
  if (!this._hasIterator(promises)) {
    throw new Error('Parameters are not iterable');
  }

  return new MyPromise((resolve, reject) => {
    promises.forEach((promise) => {
      promise.then((res) => {
        resolve(res);
      });
    });
  });
};

Helper methods:deferred

To facilitate the creation of a Promise object that resolves lazily, we added deferredthe method:

static deferred = () => {
  let dfd = {};
  dfd.promise = new MyPromise((resolve, reject) => {
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
};

This method returns an object containing promiseresolveand rejectfor subsequent use.

Summarize

Through the above implementation, we successfully created a simple but fully functional Promise class. This handwritten Promise not only has basic asynchronous operation capabilities, but also supports Promise chain calls and common Promise methods, such as thenresolverejectallallSettledraceand any.

Implementing Promise by hand is a challenging but rewarding exercise, which helps to gain a deeper understanding of how Promise works and the mechanism of asynchronous programming. I hope this blog will help you understand Promise.

Leave a Reply

Your email address will not be published. Required fields are marked *