看看这个手写的Promise吧,超牛的

260 阅读7分钟

哎嘿,是不是被这个标题骗进来了,先别急着关了,我可以狡辩。

为什么说我写的这个牛x呢,我有以下几点理由:

  1. 作为一个官方已有的实现,我们自己手写的时候,应该尽量贴近官方实现,这点在下面的实现中有一定体现;
  2. 这次的实现比以前写的都好,所以我觉得他很优秀;
  3. 求你了,看看吧!!!

起因

为啥突然又想起了手写Promise呢,还是一个阳光明媚的下午,摸鱼翻mdn文档,翻到了Promise这一章,突然发现了一些没见过的名词,什么Promise A+,PromiseLike,thenable
遇到不懂的东西嘞,那肯定是第一时间问AI呀:

promiseA+.png 于是乎,又重新了解了一下什么是Promise。像是打开了一扇新的大门啊有木有。
这段话呢就是介绍什么才是一个Pormise,我给上面这段话翻译总结一下就是:

  1. 一个 promise 是一个对象,并且有一个 then 方法,这个 then 方法可以成功或者失败之后的任务。
  2. promise 对象还需要有三个状态,两个分支:
    • 三个状态分别是 fulfilled, rejected, and pending
    • 两个分支需要处理成功和失败后的任务。
  3. 当Promise状态发生改变的时候,就会一直锁定该状态

看了官方的规范,再想一下曾经面试时候自己手写的Promise,这啥呀这是。
要不重新实现一个呢?

开始实现

时间充裕了,当然是先拆解一下整个的实现。

第一步,实现Promise里面的状态

这里有两个关键点:

  1. 有三个状态
  2. 状态会改变,一旦改变就不会再改变
// 将状态提成公共变量方便使用
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
  #value = null;
  #state = PENDING;
  constructor(callback) {
    // 结合平时使用,promise里面传递的函数立即执行
    try {
      callback(this.#resolve.bind(this), this.#reject.bind(this));
    } catch (error) {
      this.#reject(error);
    }
  }

  #changeState(state, value) {
    if (this.#state !== PENDING) {
      return;
    }
    this.#state = state;
    this.#value = value;
  }

  #resolve(data) {
    this.#changeState(FULFILLED, data);
  }

  #reject(reason) {
    this.#changeState(REJECTED, reason);
  }
}

第二步,实现then方法

这里的关键点就有点多了

  1. then方法可以有两个参数,一个执行成功的,一个执行失败的
  2. then方法的两个回调函数不是立即执行的,要放在微任务队列里面
  3. then方法有返回值是一个Promise
  4. 假设有一个p = new Promise,那么可以多次调用p.then,甚至可以在一个setTimeout之后再调用p.then,所以,当开始执行then函数回调的时候,应该有一个队列,保证每一个p.then都执行了,执行队列的时候,同一个回调函数只会执行一次
  5. 当执行队列的时候也要分各种情况,我们在代码中详细解释
// 要加入微任务队列,可以考虑封装一个函数来执行, 这几种是常见的添加微队列的方式
const createMicrotask = (callback) => {
  if (queueMicrotask) {
    queueMicrotask(callback);
    return;
  }
  if (process && process.nextTick) {
    process.nextTick(callback);
    return;
  }
  if (MutationObserver) {
    if (!observer) {
      observer = new MutationObserver(callback);
      observer.observe(pDom, { childList: true });
    }
    pDom.textContent = "1";
    return;
  }
  setTimeout(callback, 0);
};

// then的回调可以是一个Promise,会判断一个对象是不是Promise,也抽离一个方法
const isPromise = (val) => {
  // 判断是否符合promiseA+规范
  return !!(val && typeof val === "object" && typeof val.then === "function");
};

class MyPromise {
    // ... 第一步的代码,继续往后补充then的实现
    #handlers = []; // 把handler做成一个对象数组,对象里面存放回调函数,状态,then里面返回的resolve和reject
    /**
    * 处理异步操作最终完成的结果
    * @param {*} onFulfilled 成功回调
    * @param {*} onRejected 失败回调
    * @returns 返回一个promise
    */
    then(onFulfilled, onRejected) {
        // 1. 不能直接调用,要等待state状态变了之后才可以执行
        // 2. 只能先添加到队列中,等状态变了再执行
        // this.#pushHandler(onFulfilled, FULFILLED)
        // this.#pushHandler(onRejected, REJECTED)
        // 怎么执行返回的promise的resolve和reject呢?
        // 3. 一起存起来呗,等执行任务队列的时候,就一起执行了
        return new MyPromise((resolve, reject) => {
          this.#pushHandler(onFulfilled, FULFILLED, resolve, reject);
          this.#pushHandler(onRejected, REJECTED, resolve, reject);
          this.#flashHandlers();
        });
    }
    
    /**
    * 向任务队列中添加一个任务,要区分任务是成功的还是失败的
    * @param {*} handler
    * @param {*} state
    * @param {*} resolve
    * @param {*} reject
    */
    #pushHandler(handler, state, resolve, reject) {
        this.#handlers.push({
          excutor: handler,
          state: state,
          resolve: resolve,
          reject: reject,
        });
    }
    /**
    * 冲刷任务队列,这里每执行一个就丢一个,就不会再次执行了
    */
    #flashHandlers() {
        if (this.#state === PENDING) return;
        while (this.#handlers.length) {
          const handler = this.#handlers.shift();
          this.#runHandler(handler);
        }
    }
    /**
     * 重点来了,执行任务队列中的任务,要处理几个点
     * 1. 第一个重点当然是要添加微队列了。。敲黑板!!!
     * 2. 如果不是函数,直接将当前的状态,传递给下一个then,记得吧,我们可以`p.then().then()`这样执行,如果第一个then没有回调,那么promise的结果就会透传到第二个里面
     * 3. 如果是一个函数,那么就执行这个函数,如果函数有返回值,我们也要分类处理
     *  - 如果是一个普通的值,那么就直接resolve就行了;
     *  - 如果返回值是一个Promsie,测试用例
     *  const p = new Promise((resolve, reject) => {
         setTimeout(() => {
           resolve('hello')
         }, 1000)
       })
       p.then((res) => {
         return new Promise((resolve, reject) => {
           setTimeout(() => {
             resolve('hello1')
           }, 1000)
         })
       })
       .then((res) => {
         console.log(res + 'world')
       })
       - 当然,函数执行可能会有错误,如果有错误,就reject了
     */
    #runHandler({ excutor, state, resolve, reject }) {
        createMicrotask(() => {
          if (typeof excutor !== "function") {
            // 不是个函数,将当前的状态,传递给下一个then
            state === FULFILLED ? resolve(this.#value) : reject(this.#value);
            return;
          }
          try {
            if (this.#state === state) {
              const result = excutor(this.#value);
              // 如果返回的是promise,那么就等待其完成,并继续向下传递
              if (isPromise(result)) {
                result.then(resolve, reject);
              } else {
                resolve(result);
              }
            }
          } catch (error) {
            reject(error);
          }
        });
    }
}

好,写到这,我们就可以骄傲的说,我们的Promise已经完成了,那还有其他方法呢。

第三步,实现其他静态方法

这里我们先别急,有一个偷懒的方法,那就是打开mdn,搜索Promise,我们就以Promise.resolve()为例

promisemdn.jpg 看到这段话了吗,是不是有点懵,哪个看了mdn的不知道这玩意啊。
但是,容我给你翻译一下:

Promise.prototype.resolve = (data) => {
    if data is Promise
      return data;
    return Promise((res, rej) => {
      else if data is thenable
        return data.then()
      else
        return data
    })
}

这下子有没有更清晰一点呢,按照这个思路,我们就来挨个实现每一个方法,上面我们用的是ES6的类,下面我就接着之前的写了,如果有面试问到只写静态方法的,根据上面伪代码来写就好了。

class MyPromise {
  // ... 之前实现的then方法
  catch(onRejected) {
    return this.then(null, onRejected);
  }

  /**
   * 无论成功还是失败都会执行的逻辑
   * @param {*} onSettled
   */
  finally(onSettled) {
    return this.then(
      (data) => {
        typeof onSettled === "function" && onSettled();
        return data;
      },
      (reason) => {
        typeof onSettled === "function" && onSettled();
        return reason;
      }
    );
  }

  /**
   * Promise.resolve(data)
   * @param {*} data
   */
  static resolve(data) {
    if (data instanceof MyPromise) {
      return data;
    }
    return new MyPromise((resolve, reject) => {
      if (isPromise(data)) {
        data.then(resolve, reject);
      } else {
        resolve(data);
      }
    });
  }

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

  /**
   * 需要返回一个promise
   * promise中执行所有的promiseList
   * 如果全部resolve,则返回resolve
   * 有一个rejected,则返回rejected
   * 得到的结果有顺序
   * @param {Iterable} promiseList
   */
  static all(promiseList) {
    return new MyPromise((resolve, reject) => {
      const result = [];
      let count = 0;
      let resultCount = 0;
      for (const p of promiseList) {
        let index = count;
        count++;
        // 参数列表中可能有不是promise的,与其判断处理,不如全部包含在一个MyPromise.resolve中处理
        MyPromise.resolve(p).then(
          (res) => {
            result[index] = res;
            resultCount++;
            if (resultCount === count) {
              resolve(result);
            }
          },
          (error) => {
            reject(error);
          }
        );
      }
    });
  }

  static allSettled(promises) {
    return new MyPromise((resolve, reject) => {
      const result = [];
      const count = promises.length;
      let resultCount = 0;
      for (const p of promiseList) {
        let index = count;
        count++;
        // 参数列表中可能有不是promise的,与其判断处理,不如全部包含在一个MyPromise.resolve中处理
        MyPromise.resolve(p).then(
          (res) => {
            result[index] = res;
            resultCount++;
            if (resultCount === count) {
              resolve(result);
            }
          },
          (error) => {
            result[index] = error;
            resultCount++;
            if (resultCount === count) {
              resolve(result);
            }
          }
        );
      }
    });
  }
  
  static race(promises) {
    return new MyPromise((resolve, reject) => {
      for (const p of promises) {
        MyPromise.resolve(p).then(resolve, reject);
      }
    });
  }
}

结语

以上呢,我们根据官方的规范和文档,实现了一个比较完整的Promise。如果还有不明白的地方,可以留言讨论。也欢迎各位大佬指出问题。
然后,刚开始写文章,希望大佬们给点建议,最近写的比较多的,可能就是一些面试可能遇到的问题。确实现在摸鱼比较多,趁摸鱼时间总结一点。(如果工资高一点也多承担点活?哈哈哈。)
最后就是,如果真的面试遇到了,也不要心急。比如遇到了实现Promise。首先给一个心理预期,比如看了这篇文章之后想好怎么实现了then方法就好了。然后好好想一下怎么拆解这个问题,一步一步的去实现它。
好,啰里吧嗦了一大堆,希望看到这里的同学可以有一点点收获。