从0到1实现满足Promise A+规范的Promise。

109 阅读3分钟

闲聊

正好最近在筹备面试,对于不擅长背八股的我,刷题和敲代码似乎更适合我,敲一遍就当是复习了。到了手写的阶段,Promise 的重要性就不用多说了,知识点不仅仅是异步,还涉及到其它的高阶函数和发布订阅等。作为面试的高频考点,几乎是面试必考,而很多人还不是很了解事件循环,导致面试常常吃瘪。会从分析一道经典 promise 考题入手,区分 promsie 和宏任务的区别,然后再由浅入深手写源码。当然可以直接跳转到手写。

一道经典的 promise

先看题分析。答案在后面。

Promise.resolve().then((res) => {
  console.log(1);
  setTimeout(() => {
    console.log(4);
  }, 0);
});

setTimeout(() => {
  console.log(2);
  Promise.resolve().then((res) => {
    console.log(3);
  });
}, 0);

答案是 1,2,3,4。经常有小伙伴问这道题,其实还是 Event Loop 机制没有搞明白,promise 是微任务,setTimeout 是宏任务,在事件循环中会分别有微任务队列和宏任务队列来存储,微任务会在宏任务之前执行(当然有大佬文章是宏任务在微任务前,把 Script 当作宏任务最先执行也不影响),所以会先将第一个 promise 入队,注意是入队并不是放到主线程执行,所以第一个 setTimeout 此时还并没有执行。接着主线程执行到第二个 setTimeout,就将第二个 setTimeout 入宏任务队列。
但此时 第二个 setTimeout 并不能执行,因为宏任务必须等待当前所有微任务队列的任务执行完毕才能执行,因此此时第一个 Promise 放到主线程开始执行,第一个 Promise 打印 1,并且把第一个 setTiimeout 入宏任务队列,此时是微任务队列为空,宏任务队列有两个 setTimeout 的回调,所以开始执行先入队的第二个 setTimeout,打印 2,并将第二个 Promise 入队,此时微任务队列又有东西了,第一个 setTimeout 等待,执行第二个 Promise。打印 3,最后执行第一个 setTimeout 打印 4。打印结果也就是 1,2,3,4。
说得可能有点绕,懒没做图了,因为毕竟重点是手写 Promise,自己敲一遍运行然后多看几遍会好很多。

手写 Promise

手写注意什么

比起直接啪啪一顿敲。 Promise 手写之前首先要搞清楚到底需要解决哪些问题,比如 new Promise 的时候传递的回调函数何时执行,参数 resolve 和 reject 又是从何而来,一个 promise 为什么可以 then 多次,为什么 then 是异步,为什么我在回调函数使用定时器 then 依然可以打印结果,then中返回promise或者抛出异常会如何?带着这些问题来,自然就能收货满满地走。

先实现构造函数

经常使用 new Promise((resolve,reject)=>{}),所以 Promise 是一个类或者叫构造函数,我们会在构造函数中传入一个函数作为参数,该参数立即执行,resolve 和 reject 拿来就用也是因为 Promise 内部帮我们实现了这两个函数。

// 定义Promise的三种状态。
const PNEDING = "pengding";
const FULLFILED = "fullfiled";
const REJECT = "reject";

class Promise {
  constructor(executor) {
    this.state = PNEDING; //初始状态置为等待
    this.result = undefined; //成功结果
    this.error = undefined; //失败原因
    const resolve = (res) => {
      if (this.state == PNEDING) {
        this.result = res;
        this.state = FULLFILED;
      }
    };
    const reject = (err) => {
      if (this.state == PNEDING) {
        this.error = err;
        this.state = REJECT;
      }
    };
    try {
      executor(resolve, reject);
    } catch (error) {
      //如果捕获异常直接reject
      reject(error);
    }
  }
}

实现.then

通过构造函数我们已经可以可以将一般情况下的 Promise 值进行存储。但是没有 then 就没有办法拿到值。

干饭中。。稍后更