preface

Recently, in the process of reading and learning, I found that I was not familiar with Promise and the relationship between macro tasks and micro tasks, so I decided to learn from the strengths of different schools and implement a Promise by myself. Of course, even though this kind of behavior has become bad now, I still want to be different. So we use typescript and class.

The basic interface

First and foremost are Promise’s three unique states

type StatusType = 'pending' | 'fulfilled' | 'rejected';
Copy the code

Then there is executor, the function passed in for declaration

interfaceOnfulfilledType<T = unknown> { (v? : T): unknown; }interfaceOnrejectedType { (e? :Error): unknown;
}

interface ExecutorType<T = unknown> {
  (resolve: OnfulfilledType<T>, reject: OnrejectedType): void;
}
Copy the code

At this point, we can declare a simple IPromise Class

class IPromise<T = unknown> {
    constructor(executor? : ExecutorType
       ) {
      // Attributes such as status are not declared on IPromise objects so that they are not exposed
      let status: StatusType = 'pending'; // Unique state
      let value: unknown = null; // Resolve the accepted value
      let reason: Error = new Error('IPromise Error'); // reject Indicates the received value

    const resolve: OnfulfilledType<T> = (v) = > {
      if (status === 'pending') {
        status = 'fulfilled'; value = v; }};const reject: OnrejectedType = (e) = > {
      if (status === 'pending') {
        status = 'rejected';
        if (e && isError(e)) {
          reason = e;
        } else {
          throw new Error('IPromise reason should be Error Type'); }}};if (executor) {
      try {
        executor(resolve, reject);
      } catch(error) { reject(error); }}}}Copy the code

Try new for an object

const promise: IPromise<string> = new IPromise((resolve) = > {
  resolve('promise'); // An error is reported if a number is passed
});
Copy the code

Okay, now that you’ve warmed up, you’re ready to really implement IPromise.

Complete implementation

Come to think of it, what does a Promise object do

then

  1. Gets the value of resolve or Reject when the Promise object is declared
  2. Two functions can be accepted as optional arguments
  3. Put the functions in then into the next microtask to execute
  4. Chain calls
  5. Multiple THEN calls simultaneously
  6. Promise penetration, which means that if the argument received by then is not a function, the result of the resolution will be penetrated into the next THEN

catch

  1. Gets the reject value
  2. Chain calls
  3. Multiple catches are called simultaneously
  4. Promise to penetrate

resolve

Return a fulfilled Promise instance

reject

Return an instance of Promise with the status Rejected

all

Accept an array of Promise instances and return a Promise instance whose resolve is Rejected, reject, reject

race

Receives an array of Promise instances and returns a Promise instance that resolves the first result

Resolve, Reject, All, and race are private Promise methods and will not be inherited by the instance, so they are implemented static

Offer him!

type StatusType = 'pending' | 'fulfilled' | 'rejected';

interfaceOnfulfilledType<T = unknown> { (v? : T): unknown; }interfaceOnrejectedType { (e? :Error): unknown;
}

interface ExecutorType<T = unknown> {
  (resolve: OnfulfilledType<T>, reject: OnrejectedType): void;
}

interface ResolvePromiseType {
  (promise: IPromise, result: unknown, resolve: OnfulfilledType, reject: OnrejectedType): void;
}

const isFn = (fn: unknown) = > typeof fn === 'function';

const isError = (e: unknown) = > e instanceof Error;

const onCorrect: OnfulfilledType = (v) = > v;

const onError: OnrejectedType = (e) = > {
  throw e;
};

const handleFnList = (fnList: Function[]) = > {
  if (fnList.length) fnList.forEach((fn) = > fn());
};

// When executing then,
const resolvePromise: ResolvePromiseType = (promise, result, resolve, reject) = > {
  if (result === promise) {
    // If result is the current promise, an infinite loop is triggered
    reject(new Error('Chaining cycle detected for promise #<Promise>'));
  } else if (result instanceof IPromise) {
    // If result is also a Promise instance, then pass result the resolve and reject of the promise as ondepressing and onrejectd of Result
    result.then(resolve, reject);
  } else resolve(result); // If not promise, reslove
};

class IPromise<T = unknown> {
  // Promise.resolve 
  static resolve = (v: unknown) = > {
    if (v instanceof IPromise) return v;
    return new IPromise((resolve) = > resolve(v));
  };
  // Promise.reject 
  static reject = (e: Error) = > {
    return new IPromise((_, reject) = > {
      if (isError(e)) reject(e);
      else {
        throw new TypeError('IPromise reason should be Error Type'); }}); };// Promise.all
  static all = (promiseList? : IPromise[]) = > {
    if (!Array.isArray(promiseList)) throw new TypeError('The argument should be an array! ');
    return new IPromise((resolve, reject) = > {
      try {
        const results: unknown[] = [];
        promiseList.forEach((promise) = > {
          if (promise instanceof IPromise) {
            promise.then((v) = > {
              results.push(v);
              if (results.length === promiseList.length) resolve(results);
            });
          } else reject(new TypeError('Every item of argument should be IPromise! '));
        });
      } catch(error) { reject(error); }}); };// Promise.race
  static race = (promiseList? : IPromise[]) = > {
    if (!Array.isArray(promiseList)) throw new TypeError('The argument should be an array! ');
    return new IPromise((resolve, reject) = > {
      try {
        const result: unknown[] = [];
        promiseList.forEach((promise) = > {
          if (promise instanceof IPromise) {
            promise.then((v) = > {
              if(! result.length) result.push(v);else resolve(result[0]);
            });
          } else reject(new TypeError('Every item of argument should be IPromise! '));
        });
      } catch(error) { reject(error); }}); }; then:(onfulfilled? : OnfulfilledType, onRejected? : OnrejectedType) = > IPromise;
  catch: (catchFn: OnrejectedType) = > IPromise;

  constructor(executor: ExecutorType<T>) {
    let status: StatusType = 'pending'; // Unique state
    let value: unknown = null; // Resolve the accepted value
    let reason: Error = new Error('IPromise Error'); // reject Indicates the received value
    If resolve is asynchronous, use an array to hold the required method, reject
    const onfulfilledList: OnfulfilledType[] = [];
    const onrejectedList: OnrejectedType[] = [];
    // Then methods for each instance
    this.then = (onfulfilled = onCorrect, onRejected = onError) = > {
      const promise = new IPromise((resolve, reject) = > {
        // Implement Promise penetration
        const f = isFn(onfulfilled) ? onfulfilled : onCorrect;
        const r = isFn(onRejected) ? onRejected : onError;
        // Resolution successful callback
        const newF = () = > {
          // Use queueMicrotask to simulate asynchronous microtasks
          queueMicrotask(() = > {
            try {
              resolvePromise(promise, f(value), resolve, reject);
            } catch(error) { reject(error); }}); };// Resolution failed callback
        const newR = () = > {
          queueMicrotask(() = > {
            try {
              resolvePromise(promise, r(reason), resolve, reject);
            } catch(error) { reject(error); }}); };if (status === 'fulfilled') newF();
        if (status === 'rejected') newR();
        // If resolve is executed asynchronously, save the callback until resolve is executed, reject likewise
        if (status === 'pending') { onfulfilledList.push(newF); onrejectedList.push(newR); }});return promise;
    };
    // The catch method of the instance
    this.catch = (catchFn) = > this.then(undefined, catchFn);
    // Resolve received when the instance is initialized
    const resolve: OnfulfilledType<T> = (v) = > {
      if (status === 'pending') {
        status = 'fulfilled'; value = v; handleFnList(onfulfilledList); }};// Reject received when the instance is initialized
    const reject: OnrejectedType = (e) = > {
      if (status === 'pending') {
        status = 'rejected';
        if (e && isError(e)) {
          reason = e;
          handleFnList(onrejectedList);
        } else {
          throw new Error('IPromise reason should be Error Type'); }}};// Execute immediately upon instance initialization
    if (executor) {
      if (isFn(executor)) {
        try {
          executor(resolve, reject);
        } catch(error) { reject(error); }}else reject(new TypeError('new IPromise() should receive a Function argument! '));
    } else reject(new Error('new IPromise() should receive an arguments! ')); }}export default IPromise;

Copy the code

OK, a simple Promise came true!