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 #9

Open
Hongbusi opened this issue Jun 9, 2022 · 5 comments
Open

手写题:Promise #9

Hongbusi opened this issue Jun 9, 2022 · 5 comments

Comments

@Hongbusi
Copy link
Member

Hongbusi commented Jun 9, 2022

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

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

  • 这个回调函数会立即执行,并且传入另两个回调函数 resolvereject
  • 当调用 resolve 回调函数时,会执行 Promise 对象 then 方法传入的回调函数;
  • 当调用 reject 回调函数时,会执行 Promise 对象的 catch 方法传入的回调函数。

3 种状态

  • 待定 pending:执行 executor 中的代码时,处于该状态;
  • 已兑现 fulfilled:执行了 resolve 时,处于该状态;
  • 已拒绝 rejected:执行了 reject 时,处于该状态。

9 个方法

  • then:当调用 resolve 回调函数时,会执行;
  • catch:当调用 reject 回调函数时,会执行;
  • finally:无论是 fulfilled 状态,还是 reject 状态,它都会执行;
  • resolve:相当于 new Promise,并且执行 resolve 操作;
  • reject:相当于 new Promise,并且执行 reject 操作;
  • all:将多个 Promise 包裹在一起形成一个新的 Promise
  • allSettled:所有的 Promise 都有结果,才会有最终的状态;
  • race:多个 Promise 相互竞争,谁先有结果,那么就使用谁的结果;
  • any:等到一个 fulfilled 状态,才会决定新 Promise 的状态。如果所有的 Promise 都是 reject 的,那么会报一个 AggregateError 的错误。

手写 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))
          }
        })
      })
    })
  }
}
@Hongbusi Hongbusi added handwritten-code today 每日一题。 labels Jun 9, 2022
@betteroneday
Copy link

image

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

@baboon-king
Copy link

支持 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
Copy link
Member

luckept commented Jun 9, 2022

先说结论,洪佬的是正确的,我这个版本比较简陋,细节上是有问题的,仅供大家参考一下哈哈哈,因为我写的 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 Hongbusi changed the title 手写题:Promise 手写 Promise Jun 10, 2022
@Hongbusi Hongbusi removed the today 每日一题。 label Jun 10, 2022
@Hongbusi
Copy link
Member Author

@luckept

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

``` ts

@luckept
Copy link
Member

luckept commented Jun 10, 2022

@luckept

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

``` ts

哈哈哈好!

@Hongbusi Hongbusi changed the title 手写 Promise 手写题:Promise Jun 10, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

4 participants