使用queueMicrotask+ts实现最优的Promise

470 阅读4分钟

使用queueMicrotask实现Promise

前要

关于Promise系列,我从使用,实现,原理到前置知识在前面的文章一一说过。但在这篇文章Promise的实现,我们使用setTimeout来达到异步的效果。但很明显,setTimeout是宏任务,而真正的Promise属于微任务,所以setTimeout是过时的,。因此本文再次实现Promise,通过一个apiqueueMicrotask

queueMicrotask

它就是一个比较底层的api,将一个任务放入微任务队列,我们通过一个案例来试一下,对微任务宏任务还不是很理解的可以看看我前面的文章eventLoop

setTimeout(() => {
  console.log("timeout");
});
queueMicrotask(test);
function test() {
  console.log("queueMicrotask1");
}

new Promise((res: Function, rej) => {
  res();
}).then(() => {
  console.log("promise");
});
function test2() {
  console.log("queueMicrotask2");
}
queueMicrotask(test2);
console.log("start");

//start  queueMicrotask1  promise  queueMicrotask2  timeout

我们可以分析一下:微任务先是queueMicrotask,再是promise,再是queueMicrotask,而输出也是按照这样的顺序输出的的。因此不难看出queueMicretask和promise一样同属微任务,甚至它们的优先级也是一致的,不同于node的apiprocess.nextTick,它的优先级会更高!这样,我们就可以用它来实现Promise,更加的符合Promise的执行顺序。

实现

class _Promise {
  private status: string = "padding";
  private value: any = null;
  private error: any = null;
  //   可以设置不同的类型value
  private onResolveCallback: Function[] = [];
  private onRejectCallback: Function[] = [];
  //   待执行的回调数组
  private extuc: Function;
  private self = this;
  constructor(extuc: Function) {
    this.extuc = extuc;
    try {
      //   this.extuc(this.resolve.bind(this), this.reject.bind(this));
      this.extuc(this.resolve, this.reject);
    } catch (err) {
      this.reject(err);
    }
  }
  //   resolve,reject没什么好说的,异步存入微任务队列,如果有then,执行下一级的then方法
  public resolve = (val?: any) => {
    queueMicrotask(() => {
      if (this.status === "padding") {
        this.status = "resolve";
        this.value = val;
        this.onResolveCallback.forEach((f) => {
          f(this.value);
        });
      }
    });
  };

  public reject = (err?: any) => {
    queueMicrotask(() => {
      if (this.status === "padding") {
        this.status = "reject";
        this.error = err;
        this.onRejectCallback.forEach((f) => {
          f(this.error);
        });
      }
    });
  };

  //   ts的promise的then方法两个参数都是可传课不传的function,我这里也这样实现,并约束必须时传入函数,ts的promise也是一样的
  then = (onResolve?: Function, onReject?: Function) => {
    // 设置保存this
    const s = this.self;

    // 处理没有传参的情况,将它们处理未函数
    if (!onResolve) {
      onResolve = (v?: any): void => {};
    }
    if (!onReject) {
      onReject = (v?: any): void => {};
    }

    // reslove 我不知道它的调用时机,即什么情况下调用then方法时status为resolve,有小伙伴知道吗
    if (this.status === "resolve") {
      console.log("调用resolve");
      return new _Promise((resolve: Function, reject: Function) => {
        //需要异步
        queueMicrotask(() => {
          const res = (<Function>onResolve)(s.value);
          //因为onresolve已经确定是一个函数了,我们直接断言(<Function> 变量) 也可以 (变量 as Funtion)
          if (res === _Promise) {
            res.then(resolve, reject);
            // 处理传入的Promise链式调用,会将新的异步加入微任务队列
          } else {
            resolve(res);
            // resolve即可
          }
        });
      });
    }
    // reject同上
    if (this.status === "reject") {
      return new _Promise((resolve: Function, reject: Function) => {
        queueMicrotask(() => {
          if (onReject) {
            const res = onReject(this.error);
            if (res === _Promise) {
              res.then(resolve, reject);
            } else {
              reject(res);
            }
          } else {
            reject(this.error);
          }
        });
      });
    } else {
      // 剩下可以肯定是padding
      // 这里之所以没有异步执行,是为了先同步走完,然后再去执行微任务队列(resolve添加的)再来执行里面的代码
      return new _Promise((resolve: Function, reject: Function) => {
        // 他push的函数在微任务队列执行,而本身push这只是一个同步的任务,以此达到多级then(注意,不是同一级多个then)只要有一个改变状态,假设是resolve,后面回调数组的执行时,都是执行resolve回调
        this.self.onResolveCallback.push(() => {
          try {
            const res = (<Function>onResolve)(s.value);
            if (res instanceof _Promise) {
              // 如果返回了Promise,那么就让它执行下去
              res.then(resolve, reject);
            } else {
              resolve(res);
              //   不是就直接resolve,继续往下走
            }
          } catch (e) {
            reject(e);
          }
        });

        // 失败回调类似上面成功回调
        this.self.onRejectCallback.push(() => {
          try {
            if (!onReject) throw "wihout reject callback";
            const res = onReject(s.error);
            if (res instanceof _Promise) {
              // 如果返回了Promise,那么就让它执行下去
              //     ****还没有考虑返回的Promise的状态
              res.then(resolve, reject);
            } else {
              resolve(res);
              //   不是就直接resolve,继续往下走
            }
          } catch (e) {
            reject(e);
          }
        });
      });
    }
  };

    //静态方法,直接Promise.resolve
  static resolve = (val?: any) => {
    return new _Promise((resolve: Function, reject: Function) => {
      resolve(val);
    });
  };

  static reject = (err?: any) => {
    return new _Promise((resolve: Function, reject: Function) => {
      if (err) {
        reject(err);
      } else {
        reject();
      }
    });
  };
}

几个问题

1.我不确定什么情况下then被调用是status不是padding,有小伙伴知道吗,评论区见哦!

  1. 值的穿透未解决

  2. catch,race,all等方法,我相信只要把上面的案例看懂了,小伙伴们肯定能写出来,还没有掌握的小伙伴可以试着去实现这几个方法来验证自己的掌握程度。

总结

本次文章可能是Promise的最后一篇文章。小伙伴如果还不懂或者有不理解的地方,我Promise专栏前面也有很多前置文章,可以去看看!

结语

本次的文章到这里就结束啦!♥♥♥读者大大们认为写的不错的话点个赞再走哦 ♥♥♥ 我们一起学习!一起进步!