keiko blog
Load MMD

手搓 TypeScript Promise 实现记录

充分利用 TypeScript 的优势来做符合 Promise/A+ 规范的方法

手搓 TypeScript Promise 实现记录

定义枚举状态

TypeScript
enum State {
  PENDING = "pending",
  RESOLVED = "resolved",
  REJECTED = "rejected",
}

构建基础类

TypeScript
type Resolve<T> = (value?: T) => void;

type Reject = (reason: any) => void;

type Executor<T> = (resolve: Resolve<T>, reject: Reject) => void;

class TypePromise<T = unknown> {
  private state: State = State.PENDING;
  private value?: T;
  private reason: any;

  constructor(executor: Executor<T>) {
    executor(this.resolve.bind(this), this.reject.bind(this));
  }

  private resolve(value?: T) {
    if (this.state === State.PENDING) {
      this.state = State.FULFILLED;
      this.value = value;
    }
  }

  private reject(reason: any) {
    if (this.state === State.PENDING) {
      this.state = State.REJECTED;
      this.reason = reason;
    }
  }
}

then 方法实现

Promise中最重要的一环就是.then方法的实现,这里我们先做一个简单的版本

TypeScript
class TypePromise<T = unknown> {
  ...

  public then<V = unknown>(
    onFulfilled: (value: T) => V,
    onRejected: (reason: any) => any,
  ) {
    if (this.state === State.FULFILLED) {
      onFulfilled(this.value as T);
    } else if (this.state === State.REJECTED) {
      onRejected(this.reason as any);
    }
  }
}

初步测试

这里生成一个随机数来测试调用resolve还是reject, 当调用了resolve后它应该不执行reject

TypeScript
new TypePromise((resolve, reject) => {
  const i = Math.random() * 10;

  console.log({ i });

  if (i < 5) {
    resolve("success");
  }

  reject("failed");
}).then(
  (res) => {
    console.log(res);
  },
  (err) => {
    console.log(err);
  },
);

异步的支持

首先添加回调的队列

TypeScript
class TypePromise<T = unknown> {
  ...

  private onFulfilledCallbacks: Resolve<T>[] = [];
  private onRejectedCallbacks: Reject[] = [];

  ...
}

由于是异步函数,在构造函数做个try catch以防意外。

TypeScript
class TypePromise<T = unknown> {
  ...

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

  ...
}

reasonreject方法做队列的执行处理,这里我们使用QueueMicrotask

TypeScript
class TypePromise<T = unknown> {
  ...

  private resolve(value?: T) {
    if (this.state === State.PENDING) {
      this.state = State.FULFILLED;
      this.value = value;

      this.onFulfilledCallbacks.forEach((task) => {
        queueMicrotask(() => task(this.value));
      });
    }
  }

  private reject(reason: any) {
    if (this.state === State.PENDING) {
      this.state = State.REJECTED;
      this.reason = reason;

      this.onRejectedCallbacks.forEach((task) => {
        queueMicrotask(() => task(this.reason));
      });
    }
  }

  ...
}

还需要在then上做一些工作,使其也支持队列模式

TypeScript
class TypePromise<T = unknown> {
  ...

  public then<V = unknown>(
    onFulfilled?: (value?: T) => V,
    onRejected?: (reason: any) => any,
  ) {
    return new TypePromise<V>((resolve, reject) => {
      const handleFulfilled = (value?: T) => {
        try {
          const result = onFulfilled ? onFulfilled(value) : value;

          resolve(result as V);
        } catch (e) {
          reject(e);
        }
      };

      const handleRejected = (reason: any) => {
        try {
          const result = onRejected ? onRejected(reason) : reason;

          reject(result);
        } catch (e) {
          reject(e);
        }
      };

      if (this.state === State.FULFILLED) {
        queueMicrotask(() => handleFulfilled(this.value));
      } else if (this.state === State.REJECTED) {
        queueMicrotask(() => handleRejected(this.reason));
      } else {
        this.onFulfilledCallbacks.push(handleFulfilled);
        this.onRejectedCallbacks.push(handleRejected);
      }
    });
  }
}

此时我们可以使用最经典的异步方法来测试下

TypeScript
new TypePromise((resolve) => {
  setTimeout(() => {
    resolve("success frist");
  });
})
  .then((res) => {
    console.log(res);
    return "success second";
  })
  .then((res) => {
    console.log(res);
  });

完整代码

TypeScript
enum State {
  PENDING = "pending",
  FULFILLED = "fulfilled",
  REJECTED = "rejected",
}

type Resolve<T> = (value?: T) => void;

type Reject = (reason: any) => void;

type Executor<T> = (resolve: Resolve<T>, reject: Reject) => void;

class TypePromise<T = unknown> {
  private state: State = State.PENDING;
  private value?: T;
  private reason: any;
  private onFulfilledCallbacks: Resolve<T>[] = [];
  private onRejectedCallbacks: Reject[] = [];

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

  private resolve(value?: T) {
    if (this.state === State.PENDING) {
      this.state = State.FULFILLED;
      this.value = value;

      this.onFulfilledCallbacks.forEach((task) => {
        queueMicrotask(() => task(this.value));
      });
    }
  }

  private reject(reason: any) {
    if (this.state === State.PENDING) {
      this.state = State.REJECTED;
      this.reason = reason;

      this.onRejectedCallbacks.forEach((task) => {
        queueMicrotask(() => task(this.reason));
      });
    }
  }

  public then<V = unknown>(
    onFulfilled?: (value?: T) => V,
    onRejected?: (reason: any) => any,
  ) {
    return new TypePromise<V>((resolve, reject) => {
      const handleFulfilled = (value?: T) => {
        try {
          const result = onFulfilled ? onFulfilled(value) : value;

          resolve(result as V);
        } catch (e) {
          reject(e);
        }
      };

      const handleRejected = (reason: any) => {
        try {
          const result = onRejected ? onRejected(reason) : reason;

          reject(result);
        } catch (e) {
          reject(e);
        }
      };

      if (this.state === State.FULFILLED) {
        queueMicrotask(() => handleFulfilled(this.value));
      } else if (this.state === State.REJECTED) {
        queueMicrotask(() => handleRejected(this.reason));
      } else {
        this.onFulfilledCallbacks.push(handleFulfilled);
        this.onRejectedCallbacks.push(handleRejected);
      }
    });
  }
}

new TypePromise((resolve, reject) => {
  const i = Math.random() * 10;

  console.log({ i });

  if (i < 5) {
    resolve("success");
  }

  reject("failed");
}).then(
  (res) => {
    console.log(res);
  },
  (err) => {
    console.log(err);
  },
);

new TypePromise((resolve) => {
  setTimeout(() => {
    resolve("success frist");
  });
})
  .then((res) => {
    console.log(res);
    return "success second";
  })
  .then((res) => {
    console.log(res);
  });