xiaotiandada/blog

Promise的源码实现

Opened this issue · 2 comments

参考文章

// Promises/A+
// https://promisesaplus.com/

type Resolve<T> = (value?: T | PromiseLike<T>) => void;
type Reject = (reason?: any) => void;
type onFinally = (() => void) | undefined | null;

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

enum Status {
  PENDING = 'pending',
  FULFILLED = 'fulfilled',
  REJECTED = 'rejected',
}

const isFunction = (value: any): value is Function => typeof value === 'function';

class PromiseCustom<T> {
  private status: Status;
  private value: T | null;
  private reason: any;
  private onFulfilledCallbacks: ((value: T) => void)[];
  private onRejectedCallbacks: ((reason: any) => void)[];

  constructor(executor: Executor<T>) {
    this.status = Status.PENDING; // 初始状态为 pending
    this.value = null; // 初始化 value
    this.reason = null; // 初始化 reason

    // 构造函数里面添加两个数组存储成功和失败的回调
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    try {
      executor(this.resolve, this.reject);
    } catch (error) {
      this.reject(error);
    }
  }

  // resolve 方法参数是 value
  private resolve = (value?: T | PromiseLike<T>): void => {
    if (this.status === Status.PENDING) {
      this.status = Status.FULFILLED;
      this.value = value as T;
      // resolve 里面将所有成功的回调拿出来执行
      this.onFulfilledCallbacks.forEach((callback) => {
        callback(this.value!);
      });
    }
  };

  // reject 方法参数是 reason
  private reject = (reason?: any): void => {
    if (this.status === Status.PENDING) {
      this.status = Status.REJECTED;
      this.reason = reason;
      this.onRejectedCallbacks.forEach((callback) => {
        callback(this.reason);
      });
    }
  };

  public then(onFulfilled?: Resolve<T>, onRejected?: Reject) {
    // 如果onFulfilled不是函数,给一个默认函数,返回value
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value;
    // 如果onRejected不是函数,给一个默认函数,抛出reason的Error
    onRejected =
      typeof onRejected === 'function'
        ? onRejected
        : (reason: any) => {
            throw reason;
          };

    let promiseCustomReturn = new PromiseCustom((resolve, reject) => {
      if (this.status === Status.FULFILLED) {
        setTimeout(() => {
          try {
            let x = onFulfilled!(this.value as T);
            resolvePromise(promiseCustomReturn, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      } else if (this.status === Status.REJECTED) {
        setTimeout(() => {
          try {
            let x = onRejected!(this.reason);
            resolvePromise(promiseCustomReturn, x, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      } else if (this.status === Status.PENDING) {
        // 如果还是PENDING状态,将回调保存下来
        this.onFulfilledCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled!(this.value as T);
              resolvePromise(promiseCustomReturn, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          });
        });

        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected!(this.reason);
              resolvePromise(promiseCustomReturn, x, resolve, reject);
            } catch (error) {
              reject(error);
            }
          });
        });
      }
    });

    return promiseCustomReturn;
  }

  public catch(onRejected: Reject) {
    this.then(undefined, onRejected);
  }

  public finally(onfinally?: onFinally): any {
    return this.then(
      (value: any) =>
        PromiseCustom.resolve(isFunction(onfinally) ? onfinally() : onfinally).then(() => value),
      (reason: any) =>
        PromiseCustom.resolve(isFunction(onfinally) ? onfinally() : onfinally).then(() => {
          throw reason;
        }),
    );
  }

  static resolve(parameter: any) {
    if (parameter instanceof PromiseCustom) {
      return parameter;
    }

    return new PromiseCustom((resolve) => {
      resolve(parameter);
    });
  }

  static reject(reason: any) {
    return new PromiseCustom((_, reject) => {
      reject(reason);
    });
  }

  static all(promiseList: any[]) {
    return new PromiseCustom((resolve, reject) => {
      if (!Array.isArray(promiseList)) {
        return reject(new TypeError('Argument is not iterable'));
      }

      if (promiseList.length === 0) {
        return resolve([]);
      }

      let result: any[] = [];

      promiseList.forEach((promise, index) => {
        PromiseCustom.resolve(promise).then(
          (value) => {
            result[index] = value;
            if (index >= promiseList.length) {
              resolve(result);
            }
          },
          (reason) => {
            reject(reason);
          },
        );
      });
    });
  }

  static race(promiseList: any[]) {
    return new PromiseCustom((resolve, reject) => {
      if (!Array.isArray(promiseList)) {
        return reject(new TypeError('Argument is not iterable'));
      }

      if (promiseList.length === 0) {
        return resolve();
      } else {
        for (let i = 0; i < promiseList.length; i++) {
          PromiseCustom.resolve(promiseList[i]).then(
            (value) => {
              return resolve(value);
            },
            (reason) => {
              return reject(reason);
            },
          );
        }
      }
    });
  }

  static allSettled(promiseList: any[]) {
    return new PromiseCustom((resolve, reject) => {
      if (!Array.isArray(promiseList)) {
        return reject(new TypeError('Argument is not iterable'));
      }

      let result: any[] = [];
      if (promiseList.length === 0) {
        return resolve(result);
      } else {
        for (let i = 0; i < promiseList.length; i++) {
          PromiseCustom.resolve(promiseList[i]).then(
            (value) => {
              result[i] = {
                status: Status.FULFILLED,
                value: value,
              };

              if (i >= promiseList.length - 1) {
                return resolve(result);
              }
            },
            (reason) => {
              result[i] = {
                status: Status.REJECTED,
                reason: reason,
              };

              if (i >= promiseList.length - 1) {
                return resolve(result);
              }

              return resolve(result);
            },
          );
        }
      }
    });
  }

  static any(promiseList: any[]) {
    let resultError: any = [];
    return new PromiseCustom((resolve, reject) => {
      if (!Array.isArray(promiseList)) {
        return reject?.(new TypeError('Argument is not iterable'));
      }

      if (promiseList.length === 0) {
        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError
        return reject(new TypeError('All promises were rejected'));
      } else {
        promiseList.forEach((promise, index) => {
          PromiseCustom.resolve(promise).then(
            (value) => {
              resolve(value);
            },
            (reason) => {
              resultError.push(reason);
              if (index >= promiseList.length) {
                // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError
                reject(new TypeError(resultError));
              }
            },
          );
        });
      }
    });
  }

  // promises-aplus-tests
  // adapter.deferred
  static deferred() {
    const result: any = {};
    result.promise = new PromiseCustom((resolve, reject) => {
      result.resolve = resolve;
      result.reject = reject;
    });
    return result;
  }
}

function resolvePromise<T>(promise: PromiseCustom<T>, x: any, resolve: Resolve<T>, reject: Reject) {
  if (promise === x) {
    // 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
    // 这是为了防止死循环
    return reject(new TypeError('The promise and the return value are the same'));
  }

  if (x instanceof PromiseCustom) {
    // 如果 x 为 Promise ,则使 promise 接受 x 的状态
    // 也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
    x.then((y: any) => {
      resolvePromise(promise, y, resolve, reject);
    }, reject);
  } else if ((x && typeof x === 'object') || typeof x === 'function') {
    let called = false;
    try {
      let then = x.then;
      if (typeof then === 'function') {
        // 4) 2.3.3: Otherwise, if `x` is an object or function, 2.3.3.3: If `then` is a function, call it with `x` as `this`, first argument `resolvePromise`, and second argument `rejectPromise` 2.3.3.3.1: If/when `resolvePromise` is called with value `y`, run `[[Resolve]](promise, y)` `y` is not a thenable `y` is `null` `then` calls `resolvePromise` asynchronously via return from a rejected promise:
        then.call(
          x,
          (y: T) => {
            // 如果 resolvePromise 和 rejectPromise 均被调用,
            // 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
            if (called) return;
            called = true;
            resolvePromise(promise, y, resolve, reject);
          },
          (r: any) => {
            if (called) return;
            called = true;
            reject(r);
          },
        );
      } else {
        // 如果 then 不是函数,以 x 为参数执行 promise
        if (called) return;
        called = true;
        resolve(x);
      }
    } catch (error) {
      if (called) return;
      called = true;
      return reject(error);
    }
  } else {
    // 如果 x 不为对象或者函数,以 x 为参数执行 promise
    //  2.3.4: If `x` is not an object or function, fulfill `promise` with `x` The value is `undefined`. The value is `null`
    resolve(x);
  }
}

export = PromiseCustom;

测试用例

"use strict";

let PromiseCustom = require("./PromiseCustom");

{
  const promiseA = new PromiseCustom((resolve, reject) => {
    resolve(777);
  });
  // At this point, "promiseA" is already settled.
  promiseA.then((val) => console.log("asynchronous logging has val:", val));
  console.log("immediate logging");

  // produces output in this order:
  // immediate logging
  // asynchronous logging has val: 777
}

// ---------------------------------------------------------------------------------------

{
  const promise1 = PromiseCustom.resolve(123);

  promise1.then((value) => {
    console.log(value);
    // Expected output: 123
  });

  // ---------------------------------------------------------------------------------------

  function resolved(result) {
    console.log('Resolved');
  }

  function rejected(result) {
    console.error(result);
  }

  Promise.reject(new Error('fail')).then(resolved, rejected);
  // Expected output: Error: fail
}


// ---------------------------------------------------------------------------------------

{
  const promise1 = Promise.resolve(3);
  const promise2 = 42;
  const promise3 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, 'foo');
  });

  Promise.all([promise1, promise2, promise3]).then((values) => {
    console.log(values);
  });
  // Expected output: Array [3, 42, "foo"]
}

// ---------------------------------------------------------------------------------------

{
  const promise1 = new Promise((resolve, reject) => {
    setTimeout(resolve, 500, 'one');
  });

  const promise2 = new Promise((resolve, reject) => {
    setTimeout(resolve, 100, 'two');
  });

  Promise.race([promise1, promise2]).then((value) => {
    console.log(value);
    // Both resolve, but promise2 is faster
  });
  // Expected output: "two"
}

// ---------------------------------------------------------------------------------------

{
  const promise1 = new PromiseCustom((resolve, reject) => {
    throw new Error('Uh-oh!');
  });

  promise1.catch((error) => {
    console.error(error);
  });
  // Expected output: Error: Uh-oh!

}

// ---------------------------------------------------------------------------------------

{
  new PromiseCustom()
    .finally(() => {
      console.log('Experiment completed');
    });
}

// ---------------------------------------------------------------------------------------

{
  const promise1 = PromiseCustom.resolve(3);
  const promise2 = new PromiseCustom((resolve, reject) => setTimeout(reject, 100, 'foo'));
  const promises = [promise1, promise2];

  Promise.allSettled(promises).
    then((results) => results.forEach((result) => console.log(result.status)));

  // Expected output:
  // "fulfilled"
  // "rejected"

}

// ---------------------------------------------------------------------------------------

{
  const promise1 = Promise.reject(0);
  const promise2 = new Promise((resolve) => setTimeout(resolve, 100, 'quick'));
  const promise3 = new Promise((resolve) => setTimeout(resolve, 500, 'slow'));

  const promises = [promise1, promise2, promise3];

  Promise.any(promises).then((value) => console.log(value));

  // Expected output: "quick"

}
function executePromise(promises) {
  if (!Array.isArray(promises)) {
    return Promise.reject(new TypeError('Arguments is not iterable'));
  }

  const result = [];
  let len = promises.length;

  if (!len) {
    return Promise.resolve(result);
  }

  function executeNextPromise(index) {
    if (index >= len) {
      return Promise.resolve(result);
    }

    return promises[index]().then(
      (value) => {
        result[index] = {
          status: 'fulfilled',
          reason: value,
        };
        return executeNextPromise(index + 1);
      },
      (error) => {
        result[i] = {
          status: 'rejected',
          reason: error,
        };
        return executeNextPromise(index + 1);
      },
    );
  }

  return executeNextPromise(0);
}

// 示例用法
const promise1 = () =>
  new Promise((resolve) =>
    setTimeout(() => {
      console.log(1);
      resolve(1);
    }, 200),
  );
const promise2 = () =>
  new Promise((resolve) =>
    setTimeout(() => {
      console.log(2);
      resolve(2);
    }, 100),
  );
const promise3 = () =>
  new Promise((resolve) =>
    setTimeout(() => {
      console.log(3);
      resolve(3);
    }, 200),
  );

const promises = [promise1, promise2, promise3];

executePromise(promises)
  .then((result) => {
    console.log('done');
    console.log(result); // 输出 [1, 2, 3]
  })
  .catch((error) => {
    console.error(error);
  });