Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

基于 Promise A+ 规范手写一个 typescript 版的 Promise #17

Open
li-jia-nan opened this issue Oct 15, 2022 · 0 comments
Open

基于 Promise A+ 规范手写一个 typescript 版的 Promise #17

li-jia-nan opened this issue Oct 15, 2022 · 0 comments

Comments

@li-jia-nan
Copy link
Owner

前言

之前看到两篇关于手写Promise的文章,原文链接如下:

于是我心血来潮,把这两篇文章结合了起来:

  • 实现一个 typescript 版并且可以完美通过 Promise/A+ 官方872个测试用例的 Promise:

废话不多说,直接上代码:

  • 由于原文中解释的比较详细,再加上代码里注释也比较详细,这里就不一行一行带着写了
// 首先将三种状态提出来,用枚举管理
enum State {
  PENDING = 'pending',
  FULFILLED = 'fulfilled',
  REJECTED = 'rejected',
}

// 将需要类型提出来
type Resolve<T> = (value?: T | PromiseLike<T>) => void;
type Reject = (reason?: any) => void;
type Executor<T> = (resolve?: Resolve<T>, reject?: Reject) => void;
type onFulfilled<T, TResult1> = ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null;
type onRejected<TResult2> = ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null;
type onFinally = (() => void) | undefined | null;

// eslint-disable-next-line @typescript-eslint/ban-types
const isFunction = (value: any): value is Function => typeof value === 'function';

class MyPromise<T> {
  public PromiseResult!: T;
  public PromiseState!: State;
  private onFulfilledCallbacks: Resolve<T>[] = [];
  private onRejectedCallbacks: Reject[] = [];
  // 构造函数:通过new命令生成对象实例时,自动调用类的构造函数,给类的构造方法constructor添加一个参数func
  constructor(func: Executor<T>) {
    this.PromiseState = State.PENDING; // 指定Promise对象的状态属性 PromiseState,初始值为pending
    this.onFulfilledCallbacks = []; //保存成功回调
    this.onRejectedCallbacks = []; // 保存失败回调
    try {
      /**
       * func()传入resolve和reject,
       * resolve()和reject()方法在外部调用,这里需要注意一下this指向是否正确,如果不正确,需要用bind修正一下
       * new 对象实例时,自动执行func()
       */
      func(this.resolve, this.reject);
    } catch (err) {
      // 生成实例时(执行resolve和reject),如果报错,就把错误信息传入给reject()方法,并且直接执行reject()方法
      this.reject(err);
    }
  }
  // result为成功态时接收的终值
  private resolve: Resolve<T> = result => {
    // 只能由pedning状态 => fulfilled状态 (避免调用多次resolve reject)
    if (this.PromiseState === State.PENDING) {
      /**
       * 为什么resolve和reject要加setTimeout?
       * 2.2.4规范 onFulfilled 和 onRejected 只允许在 execution context 栈仅包含平台代码时运行.
       * 注1 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
       * 这个事件队列可以采用“宏任务(macro-task)”机制,比如setTimeout 或者 setImmediate; 也可以采用“微任务(micro-task)”机制来实现, 比如 MutationObserver 或者process.nextTick。
       */
      setTimeout(() => {
        this.PromiseState = State.FULFILLED;
        this.PromiseResult = result as T;
        /**
         * 在执行resolve或者reject的时候,遍历自身的callbacks数组,
         * 看看数组里面有没有then那边 保留 过来的 待执行函数,
         * 然后逐个执行数组里面的函数,执行的时候会传入相应的参数
         */
        this.onFulfilledCallbacks.forEach(callback => {
          callback(result);
        });
      });
    }
  };
  // reason为拒绝态时接收的终值
  private reject: Reject = reason => {
    // 只能由pedning状态 => rejected状态 (避免调用多次resolve reject)
    if (this.PromiseState === State.PENDING) {
      setTimeout(() => {
        this.PromiseState = State.REJECTED;
        this.PromiseResult = reason;
        this.onRejectedCallbacks.forEach(callback => {
          callback(reason);
        });
      });
    }
  };
  /**
   * [注册 fulfilled 状态 / rejected 状态 对应的回调函数]
   * @param {function} onFulfilled fulfilled状态时 执行的函数
   * @param {function} onRejected rejected状态时 执行的函数
   * @returns {function} newPromsie 返回一个新的promise对象
   */
  public then = <TResult1 = T, TResult2 = never>(
    onFulfilled?: onFulfilled<T, TResult1>,
    onRejected?: onRejected<TResult2>
  ): MyPromise<TResult1 | TResult2> => {
    /**
     * 参数校验:Promise规定then方法里面的两个参数如果不是函数的话就要被忽略
     * 所谓“忽略”并不是什么都不干,
     * 对于onFulfilled来说“忽略”就是将value原封不动的返回,
     * 对于onRejected来说就是返回拒因,
     * onRejected因为是错误分支,我们返回拒因时应该throw一个Error
     */
    onFulfilled = isFunction(onFulfilled)
      ? onFulfilled
      : value => {
          return value as any;
        };
    onRejected = isFunction(onRejected)
      ? onRejected
      : reason => {
          throw reason;
        };
    // 2.2.7 规范 then 方法必须返回一个 promise 对象
    const promise2 = new MyPromise<TResult1 | TResult2>((resolve, reject) => {
      if (this.PromiseState === State.FULFILLED) {
        /**
         * 为什么这里要加定时器setTimeout?
         * 2.2.4规范 onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用 注1
         * 这里的平台代码指的是引擎、环境以及 promise 的实施代码。
         * 实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
         * 这个事件队列可以采用“宏任务(macro-task)”机制,比如setTimeout 或者 setImmediate; 也可以采用“微任务(micro-task)”机制来实现, 比如 MutationObserver 或者process.nextTick。
         */
        setTimeout(() => {
          try {
            // 2.2.7.1规范 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x),即运行resolvePromise()
            const x = onFulfilled?.(this.PromiseResult)!;
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            // 2.2.7.2 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
            reject?.(e); // 捕获前面onFulfilled中抛出的异常
          }
        });
      }
      if (this.PromiseState === State.REJECTED) {
        setTimeout(() => {
          try {
            const x = onRejected?.(this.PromiseResult)!;
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject?.(e);
          }
        });
      }
      if (this.PromiseState === State.PENDING) {
        // pending 状态保存的 resolve() 和 reject() 回调也要符合 2.2.7.1 和 2.2.7.2 规范
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onFulfilled?.(this.PromiseResult)!;
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject?.(e);
            }
          });
        });
        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              const x = onRejected?.(this.PromiseResult)!;
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject?.(e);
            }
          });
        });
      }
    });
    return promise2;
  };
  /**
   * Promise.resolve()
   * @param {[type]} value 要解析为 Promise 对象的值
   */
  static resolve = <T>(value?: T | PromiseLike<T>): MyPromise<T> => {
    // 如果这个值是一个 promise ,那么将返回这个 promise
    if (value instanceof MyPromise) {
      return value;
    }
    // 否则返回的promise将以此值完成,即以此值执行`resolve()`方法 (状态为fulfilled)
    return new MyPromise(resolve => {
      resolve?.(value);
    });
  };
  /**
   * Promise.reject()
   * @param {*} reason 表示Promise被拒绝的原因
   * @returns
   */
  static reject = <T = never>(reason?: any): MyPromise<T> => {
    // 不需要额外判断
    return new MyPromise((resolve, reject) => {
      reject?.(reason);
    });
  };
  /**
   * Promise.prototype.catch()
   * @param {*} onRejected
   */
  /** */
  public catch = <TResult = never>(onrejected?: onRejected<TResult>): MyPromise<T | TResult> => {
    return this.then(null, onrejected);
  };
  /**
   * Promise.prototype.finally()
   * @param {*} onfinally 无论结果是fulfilled或者是rejected,都会执行的回调函数
   * @returns
   */
  // 无论如何都会执行,不会传值给回调函数
  public finally = (onfinally?: onFinally): MyPromise<T> => {
    return this.then(
      value =>
        MyPromise.resolve(isFunction(onfinally) ? onfinally() : onfinally).then(() => {
          return value;
        }),
      reason =>
        MyPromise.resolve(isFunction(onfinally) ? onfinally() : onfinally).then(() => {
          throw reason;
        })
    );
  };
  /**
   * Promise.all()
   * @param {iterable} promises 一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入
   * @returns
   */
  static all = <T>(promises: readonly T[]): MyPromise<T[]> => {
    return new MyPromise((resolve, reject) => {
      // 参数校验
      if (Array.isArray(promises)) {
        let result: T[] = []; // 存储结果
        let count = 0; // 计数器
        // 如果传入的参数是一个空的可迭代对象,则返回一个已完成(already resolved)状态的 Promise
        if (promises.length === 0) {
          return resolve?.(promises);
        }
        promises.forEach((item, index) => {
          // MyPromise.resolve方法中已经判断了参数是否为promise与thenable对象,所以无需在该方法中再次判断
          MyPromise.resolve(item).then(
            value => {
              count++;
              // 每个promise执行的结果存储在result中
              result[index] = value;
              // Promise.all 等待所有都完成(或第一个失败)
              if (count === promises.length) {
                resolve?.(result);
              }
            },
            reason => {
              /**
               * 如果传入的 promise 中有一个失败(rejected),
               * Promise.all 异步地将失败的那个结果给失败状态的回调函数,而不管其它 promise 是否完成
               */
              reject?.(reason);
            }
          );
        });
      } else {
        return reject?.(new TypeError('Argument is not iterable'));
      }
    });
  };
  /**
   * Promise.race()
   * @param {iterable} promises 可迭代对象,类似Array。详见 iterable。
   * @returns
   */
  static race = <T>(promises: readonly T[]): MyPromise<T extends PromiseLike<infer U> ? U : T> => {
    return new MyPromise((resolve, reject) => {
      // 参数校验
      if (Array.isArray(promises)) {
        // 如果传入的迭代promises是空的,则返回的 promise 将永远等待。
        if (promises.length) {
          promises.forEach(item => {
            /**
             * 如果迭代包含一个或多个非承诺值和/或已解决/拒绝的承诺,
             * 则 Promise.race 将解析为迭代中找到的第一个值。
             */
            MyPromise.resolve(item).then(resolve, reject);
          });
        }
      } else {
        return reject?.(new TypeError('Argument is not iterable'));
      }
    });
  };
  /**
   * Promise.allSettled()
   * @param {iterable} promises 一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入
   * @returns
   */
  static allSettled = <T>(
    promises: readonly T[]
  ): MyPromise<PromiseSettledResult<Awaited<T>>[]> => {
    return new MyPromise((resolve, reject) => {
      // 参数校验
      if (Array.isArray(promises)) {
        let result: any[] = []; // 存储结果
        let count = 0; // 计数器
        // 如果传入的是一个空数组,那么就直接返回一个resolved的空数组promise对象
        if (promises.length === 0) {
          return resolve?.(promises);
        }
        promises.forEach((item, index) => {
          // 非promise值,通过Promise.resolve转换为promise进行统一处理
          MyPromise.resolve(item).then(
            value => {
              count++;
              // 对于每个结果对象,都有一个 status 字符串。如果它的值为 fulfilled,则结果对象上存在一个 value 。
              result[index] = { status: 'fulfilled', value };
              // 所有给定的promise都已经fulfilled或rejected后,返回这个promise
              if (count === promises.length) {
                resolve?.(result);
              }
            },
            reason => {
              count++;
              /**
               * 对于每个结果对象,都有一个 status 字符串。如果值为 rejected,则存在一个 reason 。
               * value(或 reason )反映了每个 promise 决议(或拒绝)的值。
               */
              result[index] = { status: 'rejected', reason };
              // 所有给定的promise都已经fulfilled或rejected后,返回这个promise
              if (count === promises.length) {
                resolve?.(result);
              }
            }
          );
        });
      } else {
        return reject?.(new TypeError('Argument is not iterable'));
      }
    });
  };
  /**
   * Promise.any()
   * @param {iterable} promises 一个promise的iterable类型(注:Array,Map,Set都属于ES6的iterable类型)的输入
   * @returns
   */
  static any = <T>(promises: (T | PromiseLike<T>)[]): MyPromise<T> => {
    return new MyPromise((resolve, reject) => {
      // 参数校验
      if (Array.isArray(promises)) {
        let errors: any[] = []; //
        let count = 0; // 计数器
        // 如果传入的参数是一个空的可迭代对象,则返回一个 已失败(already rejected) 状态的 Promise。
        if (promises.length === 0) {
          return reject?.(new Error('All promises were rejected'));
        }
        promises.forEach(item => {
          // 非Promise值,通过Promise.resolve转换为Promise
          MyPromise.resolve(item).then(
            value => {
              // 只要其中的一个 promise 成功,就返回那个已经成功的 promise
              resolve?.(value);
            },
            reason => {
              count++;
              errors.push(reason);
              /**
               * 如果可迭代对象中没有一个 promise 成功,就返回一个失败的 promise 和 AggregateError 类型的实例,
               * AggregateError是 Error 的一个子类,用于把单一的错误集合在一起。
               */
              if (count === promises.length) {
                reject?.(new Error('All promises were rejected'));
              }
            }
          );
        });
      } else {
        return reject?.(new TypeError('Argument is not iterable'));
      }
    });
  };
}

/**
 * 对 resolve、reject 进行改造增强,针对 resolve 和 reject 中不同值的情况进行处理
 * @param  {promise} promise2 promise1.then 方法返回的新的 promise 对象
 * @param  {[type]} x         promise1 中 onFulfilled 或 onRejected 的返回值
 * @param  {[type]} resolve   promise2 的 resolve 方法
 * @param  {[type]} reject    promise2 的 reject 方法
 */
const resolvePromise = <T>(
  promise2: MyPromise<T>,
  x: T | PromiseLike<T>,
  resolve?: Resolve<T>,
  reject?: Reject
): void => {
  // 2.3.1 规范 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 reject
  if (promise2 === x) {
    return reject?.(new TypeError('Chaining cycle detected for promise'));
  }
  // 2.3.2 规范 如果 x 为 Promise ,则使 promise2 接受 x 的状态
  if (x instanceof MyPromise) {
    if (x.PromiseState === State.PENDING) {
      /**
       * 2.3.2.1 如果 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
       *         注意"直至 x 被执行或拒绝"这句话,
       *         这句话的意思是:x 被执行x,如果执行的时候拿到一个y,还要继续解析y
       */
      x.then(y => {
        resolvePromise(promise2, y, resolve, reject);
      }, reject);
    }
    if (x.PromiseState === State.FULFILLED) {
      // 2.3.2.2 如果 x 处于执行态,用相同的值执行 promise
      resolve?.(x.PromiseResult);
    }
    if (x.PromiseState === State.REJECTED) {
      // 2.3.2.3 如果 x 处于拒绝态,用相同的据因拒绝 promise
      reject?.(x.PromiseResult);
    }
  } else if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    // 2.3.3 如果 x 为对象或函数
    let then: PromiseLike<T>['then'];
    try {
      // 2.3.3.1 把 x.then 赋值给 then
      then = (x as PromiseLike<T>).then;
    } catch (e) {
      // 2.3.3.2 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
      return reject?.(e);
    }
    /**
     * 2.3.3.3
     * 如果 then 是函数,将 x 作为函数的作用域 this 调用之。
     * 传递两个回调函数作为参数,
     * 第一个参数叫做 `resolvePromise` ,第二个参数叫做 `rejectPromise`
     */
    if (typeof then === 'function') {
      // 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
      let called = false; // 避免多次调用
      try {
        then.call(
          x,
          // 2.3.3.3.1 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
          y => {
            if (called) {
              return;
            }
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          // 2.3.3.3.2 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
          r => {
            if (called) {
              return;
            }
            called = true;
            reject?.(r);
          }
        );
      } catch (e) {
        /**
         * 2.3.3.3.4 如果调用 then 方法抛出了异常 e
         * 2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
         */
        if (called) {
          return;
        }
        called = true;
        // 2.3.3.3.4.2 否则以 e 为据因拒绝 promise
        reject?.(e);
      }
    } else {
      // 2.3.3.4 如果 then 不是函数,以 x 为参数执行 promise
      resolve?.(x);
    }
  } else {
    // 2.3.4 如果 x 不为对象或者函数,以 x 为参数执行 promise
    resolve?.(x);
  }
};

// 下面这部分代码是用来跑测试用例的,和 Promise 本身无关
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
Reflect.set(MyPromise, 'deferred', () => {
  const result: Record<PropertyKey, any> = {};
  Reflect.set(
    result,
    'promise',
    new MyPromise((resolve, reject) => {
      Reflect.set(result, 'resolve', resolve);
      Reflect.set(result, 'reject', reject);
    })
  );
  return result;
});

module.exports = MyPromise;

参考链接

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant