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的源码实现 #71

Open
xiaotiandada opened this issue Mar 30, 2021 · 2 comments
Open

Promise的源码实现 #71

xiaotiandada opened this issue Mar 30, 2021 · 2 comments

Comments

@xiaotiandada
Copy link
Owner

xiaotiandada commented Mar 30, 2021

参考文章

// 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;
@xiaotiandada
Copy link
Owner Author

测试用例

"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"

}

@xiaotiandada
Copy link
Owner Author

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);
  });

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

No branches or pull requests

1 participant