TS代码实践 - Promise

45 阅读1分钟

注意,代码实现并不是 polyfill,因为微任务是浏览器层面的实现,这里我们只是去实现规范

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

type PromiseResolve<T> = (value: T) => void;
type PromiseReject = (reason?: any) => void;

class CustomPromise<T> {
  PromiseState: PromiseState = 'pending';
  PromiseResult?: any;

  private onFulfilledCallbacks: PromiseResolve<any>[] = [];
  private onRejectedCallbacks: PromiseReject[] = [];

  private resolve(value: T): void {
    if (this.PromiseState === 'pending') {
      this.PromiseState = 'fulfilled';
      this.PromiseResult = value;
      for (const callback of this.onFulfilledCallbacks) {
        callback(value);
      }
    }
  }
  private reject(reason?: any): void {
    if (this.PromiseState === 'pending') {
      this.PromiseState = 'rejected';
      this.PromiseResult = reason;
      for (const callback of this.onRejectedCallbacks) {
        callback(reason);
      }
    }
  }

  constructor(executor: (resolve: PromiseResolve<T>, reject: PromiseReject) => void) {
    try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (e) {
      this.reject(e);
    }
  }

  then<TResult1 = T, TResult2 = never>(
    onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
    onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
  ): CustomPromise<TResult1 | TResult2> {
    const promise = new CustomPromise<TResult1 | TResult2>((resolve, reject) => {
      switch (this.PromiseState) {
        case 'fulfilled':
          queueMicrotask(() => {
            if (typeof onfulfilled === 'function') {
              try {
                const x = onfulfilled(this.PromiseResult);
                promiseResolutionProcedure(promise, x, resolve, reject);
              } catch (e) {
                reject(e);
              }
            } else {
              resolve(this.PromiseResult);
            }
          });
          break;

        case 'rejected':
          queueMicrotask(() => {
            if (typeof onrejected === 'function') {
              try {
                const x = onrejected(this.PromiseResult);
                promiseResolutionProcedure(promise, x, resolve, reject);
              } catch (e) {
                reject(e);
              }
            } else {
              reject(this.PromiseResult);
            }
          });
          break;

        case 'pending':
          queueMicrotask(() => {
            this.onFulfilledCallbacks.push((value) => {
              if (typeof onfulfilled === 'function') {
                try {
                  const x = onfulfilled(this.PromiseResult);
                  promiseResolutionProcedure(promise, x, resolve, reject);
                } catch (e) {
                  reject(e);
                }
              } else {
                resolve(value);
              }
            });

            this.onRejectedCallbacks.push((reason) => {
              if (typeof onrejected === 'function') {
                try {
                  const x = onrejected(this.PromiseResult);
                  promiseResolutionProcedure(promise, x, resolve, reject);
                } catch (e) {
                  reject(e);
                }
              } else {
                reject(reason);
              }
            });
          });
          break;

        default:
          break;
      }
    });
    return promise;
  }
}

function promiseResolutionProcedure(promise: CustomPromise<any>, x: any, resolve: PromiseResolve<any>, reject: PromiseReject) {
  if (x === promise) {
    reject(new TypeError('Chaining cycle detected for promise'));
  } else if (x instanceof CustomPromise) {
    switch (x.PromiseState) {
      case 'pending':
        x.then((value) => {
          promiseResolutionProcedure(promise, value, resolve, reject);
        });
        break;

      case 'fulfilled':
        resolve(x.PromiseResult);
        break;

      case 'rejected':
        reject(x.PromiseResult);
        break;

      default:
        break;
    }
  } else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    let isCalled = false;
    try {
      const then = x.then;
      if (typeof then === 'function') {
        then.call(
          x,
          (y: any) => {
            if (!isCalled) {
              isCalled = true;
              promiseResolutionProcedure(promise, y, resolve, reject);
            }
          },
          (r?: any) => {
            if (!isCalled) {
              isCalled = true;
              reject(r);
            }
          }
        );
      } else {
        resolve(x);
      }
    } catch (e) {
      if (!isCalled) {
        isCalled = true;
        reject(e);
      }
    }
  } else {
    resolve(x);
  }
}

参考