developer-plus / interview

https://interview.developer-plus.org
MIT License
9 stars 1 forks source link

手写题:Promise #9

Open Hongbusi opened 2 years ago

Hongbusi commented 2 years ago

Promise 是 ES6 中新增的引用类型。

在通过 new 创建 Promise 对象时,需要传入一个回调函数(executor)。

3 种状态

9 个方法

手写 Promise 思路

一、Promise 类设计

class class HbsPromise {}

二、构造函数的规划

class HbsPromise {
  constructor(executor) {
    // 定义状态
    // 定义 resolve、reject 回调
    // resolve 执行微任务队列:改变状态、获取 value、then 传入执行成功回调
    // reject 执行微任务队列:改变状态、获取 reason、then 传入执行失败回调

    // try catch
    executor(resolve, reject)
  }
}

三、then 方法实现

class HbsPromise {
  then(onFulfilled, onRejected) {    
    // 1. 判断 onFulfilled、onRejected,给默认值

    // 2. 返回 Promise resolve/reject

    // 3. 判断之前的 Promise 状态是否确定
    // onFulfilled/onRejected 直接执行(捕获异常)

    // 4. 添加到数组中 push(() => { 执行 onFulfilled/onRejected 直接执行代码 })
  }
}

四、catch 方法

class HbsPromise {
  catch(onRejected) {
    return this.then(undefined, onRejected)
  }
}

五、finally

class HbsPromise {
  finally(onFinally) {
    return this.then(() => { onFinally() }, () => { onFinally() })
  }
}

六、resolve/reject

class HbsPromise {
  static resolve(value) {
    return new HbsPromise(resolve => resolve(value))
  }

  static reject(reason) {
    return new HbsPromise((resolve, reject) => reject(reason))
  }
}

七、all/allSettled

核心:要知道 new Promiseresolvereject 在什么情况下执行。

all

情况一:所有的都有结果; 情况二:有一个 reject

allSettled

情况:所有都有结果,并且一定执行 resolve

八、race/any

race

情况:只要有结果。

any

情况一:必须等到一个 resolve 结果; 情况二:都没有 resolve,所有的都是 reject,报一个 AggregateError 的错误。

手写 Promise

const PROMISE_STATUS_PENDING = 'pending'
const PROMISE_STATUS_FULFILLED = 'fulfilled'
const PROMISE_STATUS_REJECTED = 'rejected'

// 工具函数
function execFunctionWithCatchError(execFn, value, resolve, reject) {
  try {
    const result = execFn(value)
    resolve(result)
  }
  catch (err) {
    reject(err)
  }
}

class HbsPromise {
  constructor(executor) {
    this.status = PROMISE_STATUS_PENDING
    this.value = undefined
    this.reason = undefined
    this.onFulfilledFns = []
    this.onRejectedFns = []

    const resolve = (value) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        // 添加微任务
        queueMicrotask(() => {
          this.status = PROMISE_STATUS_FULFILLED
          this.value = value
          this.onFulfilledFns.forEach((fn) => {
            fn()
          })
        })
      }
    }

    const reject = (reason) => {
      if (this.status === PROMISE_STATUS_PENDING) {
        // 添加微任务
        queueMicrotask(() => {
          this.status = PROMISE_STATUS_REJECTED
          this.reason = reason
          this.onRejectedFns.forEach((fn) => {
            fn()
          })
        })
      }
    }

    try {
      executor(resolve, reject)
    }
    catch (err) {
      reject(err)
    }
  }

  then(onFulfilled, onRejected) {
    const defaultOnRejected = (err) => { throw err }
    onRejected = onRejected || defaultOnRejected

    const defaultOnFulfilled = (value) => { return value }
    onFulfilled = onFulfilled || defaultOnFulfilled

    return new HbsPromise((resolve, reject) => {
      // 1. 如果在 then 调用的时候,状态已经确定下来
      if (this.status === PROMISE_STATUS_FULFILLED) {
        execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
      }

      if (this.status === PROMISE_STATUS_REJECTED) {
        execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
      }

      // 2. 将成功回调和失败回调放到数组中
      if (this.status === PROMISE_STATUS_PENDING) {
        this.onFulfilledFns.push(() => {
          execFunctionWithCatchError(onFulfilled, this.value, resolve, reject)
        })
        this.onRejectedFns.push(() => {
          execFunctionWithCatchError(onRejected, this.reason, resolve, reject)
        })
      }
    })
  }

  catch(onRejected) {
    return this.then(undefined, onRejected)
  }

  finally(onFinally) {
    this.then(() => {
      onFinally()
    }, () => {
      onFinally()
    })
  }

  static resolve(value) {
    return new HbsPromise(resolve => resolve(value))
  }

  static reject(reason) {
    return new HbsPromise((resolve, reject) => reject(reason))
  }

  static all(promises) {
    return new HbsPromise((resolve, reject) => {
      const values = []
      promises.forEach((promise, index) => {
        promise.then((res) => {
          values[index] = res
          if (values.length === promises.length) {
            resolve(values)
          }
        }, (err) => {
          reject(err)
        })
      })
    })
  }

  static allSettled(promises) {
    return new HbsPromise((resolve) => {
      const values = []
      promises.forEach((promise, index) => {
        promise.then((res) => {
          values[index] = {
            status: PROMISE_STATUS_FULFILLED,
            value: res
          }

          if (values.length === promises.length) {
            resolve(values)
          }
        }, (err) => {
          values[index] = {
            status: PROMISE_STATUS_REJECTED,
            reason: err
          }
          if (values.length === promises.length) {
            resolve(values)
          }
        })
      })
    })
  }

  static race(promises) {
    return new HbsPromise((resolve, reject) => {
      promises.forEach((promise) => {
        promise.then(resolve, reject)
      })
    })
  }

  static any(promises) {
    return new HbsPromise((resolve, reject) => {
      const reasons = []
      promises.forEach((promise, index) => {
        promise.then(resolve, (err) => {
          reasons[index] = err
          if (reasons.length === promises.length) {
            reject(new AggregateError(reasons))
          }
        })
      })
    })
  }
}
betteroneday commented 2 years ago
image

前面那个判断能去掉吗,里面已经判断了嘛

baboon-king commented 2 years ago

支持 HBS & developer-plus🏅 补充一个答案

/**
 * 分析
 * 1. 通过 new Promise 语法发现 ,Promise 就是一个类 ,并且在创建这个类的时候需要一个参数,它会立即执行
 *
 * 2. Promise 中有3中状态,分别为 成功 fulfilled、失败 rejected 、等待 pending
 *    状态走向:
 *    pending -> fulfilled
 *    or
 *    pending -> rejected
 *    只要状态确定,将不可更改。
 *
 * 3. resolve 和 reject 就是用来更改状态的
 *    resolve:fulfilled
 *    reject:rejected
 *
 * 4. then 方法判断状态 成功调用成功回调,失败调用失败回调函数
 *
 * 5. 成功回调有一个参数 表示成功之后的值,失败回调也有一个参数表示失败之后的值
 *
 * 6. 通过 new 出来的 promise 对象 只有 then 、catch 方法成员,所以 status 、value 、等是私有属性,外部无法访问
 *    为了防止直接通过 promise.status 进行修改状态
 *
 */

/**
 * 等待
 */
const PENDING = "pending";

/**
 * 成功
 */
const FULFILLED = "fulfilled";

/**
 * 失败
 */
const REJECTED = "rejected";

/**
 * 私有属性
 * 在最新的 ES 规范中 以及有 private 关键字 来设置私有属性,这样更直观、简单
 * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Classes/Private_class_fields
 *
 * 当然在 TypeScript 中已经支持此特性。
 */
const _status = Symbol();
const _value = Symbol();
const _reason = Symbol();
const _resolve = Symbol();
const _reject = Symbol();
const _successCallback = Symbol();
const _failCallback = Symbol();

class MyPromise {
  constructor(executor) {
    // 私有属性初始化

    // 初始化为数组 解决 多次 promise.then() 情况
    this[_successCallback] = [];
    this[_failCallback] = [];

    /**
     * 成功方法
     */
    this[_resolve] = (value) => {
      // 如果状态不是等待中,直接return
      if (this[_status] !== PENDING) return;

      // 状态改为成功
      this[_status] = FULFILLED;

      // 保存成功的值
      this[_value] = value;

      // 如果成功回调存在就调用执行它
      // this[_successCallback] && this[_successCallback](value)
      while (this[_successCallback].length) this[_successCallback].shift()();
    };

    /**
     * 失败方法
     */
    this[_reject] = (reason) => {
      // 如果状态不是等待中,直接return
      if (this[_status] !== PENDING) return;

      // 状态改为失败
      this[_status] = REJECTED;

      // 保存失败的值
      this[_reason] = reason;

      // 如果失败回调存在就调用执行它
      // this[_failCallback] && this[_failCallback](reason)
      while (this[_failCallback].length) this[_failCallback].shift()();
    };

    /**
     * promise 状态 初始化
     */
    this[_status] = PENDING;

    // 立即执行执行器 并捕获错误
    try {
      executor(this[_resolve], this[_reject]);
    } catch (error) {
      this[_reject](error);
    }
  }

  then(successCallback, failCallback) {
    // 处理then 参数可选情况
    successCallback = successCallback ? successCallback : (value) => value;
    failCallback = failCallback
      ? failCallback
      : (reason) => {
          throw reason;
        };
    // then 链式调用 所以需要返回 MyPromise
    const promise = new MyPromise((resolve, reject) => {
      // 判断状态 是 成功 还是 失败
      if (this[_status] === FULFILLED) {
        // 写成异步 让new MyPromise 先创建完成
        setTimeout(() => {
          try {
            const prevVal = successCallback && successCallback(this[_value]);
            resolvePromise(promise, prevVal, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      } else if (this[_status] === REJECTED) {
        // 写成异步 让new MyPromise 先创建完成
        setTimeout(() => {
          try {
            const prevVal = failCallback && failCallback(this[_reason]);
            resolvePromise(promise, prevVal, resolve, reject);
          } catch (error) {
            reject(error);
          }
        }, 0);
      } else {
        // 异步情况
        // 把回调函数储存起来 当异步代码执行 resolve or reject 时执行回调函数
        this[_successCallback].push(() => {
          // 写成异步 让new MyPromise 先创建完成
          setTimeout(() => {
            try {
              const prevVal = successCallback && successCallback(this[_value]);
              resolvePromise(promise, prevVal, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
        this[_failCallback].push(() => {
          // 写成异步 让new MyPromise 先创建完成
          setTimeout(() => {
            try {
              const prevVal = failCallback && failCallback(this[_reason]);
              resolvePromise(promise, prevVal, resolve, reject);
            } catch (error) {
              reject(error);
            }
          }, 0);
        });
      }
    });
    return promise;
  }

  finally(callback) {
    return this.then(
      (value) => {
        return MyPromise.resolve(callback()).then(() => value);
      },
      (reason) => {
        return MyPromise.resolve(callback()).then(() => {
          throw reason;
        });
      }
    );
  }

  catch(failCallback) {
    return this.then(undefined, failCallback);
  }

  static all(arr) {
    let result = [];
    let index = 0;

    // 返回一个promise 对象
    return new MyPromise((resolve, reject) => {
      function add(key, value) {
        result[key] = value;
        index++;
        if (index === arr.length) {
          resolve(result);
        }
      }

      for (let i = 0; i < arr.length; i++) {
        const elem = arr[i];
        if (elem instanceof MyPromise) {
          elem.then(
            (value) => add(i, value),
            (reason) => reject(reason)
          );
        } else {
          add(i, elem);
        }
      }
    });
  }

  static resolve(value) {
    if (value instanceof MyPromise) return value;
    return new MyPromise((resolve) => resolve(value));
  }
}

// 辅助方法
/**
 * 判断 prevVal 是普通值还是 promise 对象
 *
 * 如果是普通值 直接调用 resolve
 *
 * 如果是 promise 对象 先查看 promise 对象的结果
 * 再根据 返回的结果 决定调用 resolve or reject
 *
 * 处理 prev 是普通值还是 promise 对象
 */
const resolvePromise = (promise, prevVal, resolve, reject) => {
  // 判断自己返回自己
  if (promise === prevVal) {
    return reject(
      new TypeError("Chaining cycle detected for promise #<Promise>")
    );
  }
  if (prevVal instanceof MyPromise) {
    // prevVal.then(value => resolve(value, reason => reject(reason))
    prevVal.then(resolve, reject);
  } else {
    // 普通值
    resolve(prevVal);
  }
};

MyPromise.defer = MyPromise.deferred = function () {
  let dfd = {};

  dfd.promise = new MyPromise((resolve, reject) => {
    dfd.resolve = resolve;

    dfd.reject = reject;
  });

  return dfd;
};
module.exports = MyPromise;

BaboonKing🙈

luckept commented 2 years ago

先说结论,洪佬的是正确的,我这个版本比较简陋,细节上是有问题的,仅供大家参考一下哈哈哈,因为我写的 then 不是微任务,会导致执行顺序有问题,正常应该在 then 中先执行完所有同步代码,再去 resolve,我这个会碰到 resolve 就去执行依赖的触发

type ResolveType = (value: any) => void
type RejectType = (value: any) => void
type Executor = (resolve: ResolveType, reject: RejectType) => void
export class Promise<T=any> {
  public resolve!: ResolveType
  public reject!: RejectType
  public status!: string
  public resolve_executor_value!: any
  public reject_executor_value!: any
  public resolve_callbacks = []
  public reject_callbacks = []

  constructor(executor: Executor) {
    this.status = 'pending' // 起始等待状态
    this.resolve = (successvalue: any): any => {
      if(this.status !== 'pending') return
      this.status = 'success'
      this.resolve_executor_value = successvalue
      this.resolve_callbacks.forEach((callback) => callback())
    }
    this.reject = (failvalue: any): any => {
      if(this.status !== 'pending') return
      this.status = 'fail'
      this.reject_executor_value = failvalue
      this.reject_callbacks.forEach((callback) => callback())
    }
    try {
      executor(this.resolve, this.reject)
    } catch (err) {
      this.status = 'pending'
      this.reject(err.toString())
      throw new Error('程序终止')
    }
  }

  then(resolveInthen: ResolveType, rejectInthen :RejectType) {
    return new Promise((resolve, reject) => {
      let result
      if(this.status === 'pending') {
        this.resolve_callbacks.push(() => {
          result = resolveInthen(this.resolve_executor_value)
          if (isPromise(result)) {
            result.then((res) => { // res 就是后面一次进来时赋成了 this.resolve_executor_value
              resolve(res) // 这个地方的 resolve 是 then 中异步 promise 的,而下面的 resolve 是原来的 promise 的,不能混用,所以这里我直接在这调用
            }, (err) => {     // 这个地方的 then 其实是存给 result 所代表的 promise,而不是 else 中的那个 promise
              reject(err)
            })
          } else {
            resolve(result)
          }
        })
        this.reject_callbacks.push(() => {
          result = rejectInthen(this.reject_executor_value)
          if (isPromise(result)) {
            result.then((res) => { // res 就是后面一次进来时赋成了 this.resolve_executor_value
              resolve(res) // 这个地方的 resolve 是 then 中异步 promise 的,而下面的 resolve 是原来的 promise 的,不能混用,所以这里我直接在这调用
            }, (err) => {     // 这个地方的 then 其实是存给 result 所代表的 promise,而不是 else 中的那个 promise
              reject(err)
            })
          } else {
            reject(result)
          }
        })
      } else {
        if (this.status === 'success') {
          resolve(resolveInthen(this.resolve_executor_value))
        }
        if (this.status === 'fail') {
          reject(rejectInthen(this.reject_executor_value))
        }
      }
    })
  }

  static all(promiseArr: Array<Promise>): Promise {
    return new Promise((resolve, reject) => {
      let allPromiseResolveSVal = []
      let new_index = 0
      promiseArr.forEach((promise, index) => {
        promise.then((res) => {
          process(res, index)
        }, (rejectFail) => {
          reject(rejectFail)
          return
        })
      })
      function process(sval, index) {
        allPromiseResolveSVal[index] = sval
        if (new_index === promiseArr.length - 1) {
          resolve(allPromiseResolveSVal)
        }
        new_index++
      }
    })
  }
}

function isPromise(val: any): val is Promise {
  return typeof val === 'object' && typeof val.then === 'function'
}

export {}
Hongbusi commented 2 years ago

@luckept

可以在代码块加一个类型,使代码高亮。

``` ts
luckept commented 2 years ago

@luckept

可以在代码块加一个类型,使代码高亮。

``` ts

哈哈哈好!