Promise原理解析

111 阅读3分钟

重要代码

  1. asap 是一个封装了微任务包;网上百度到源码基本用:setTimeout(()=>{}, 0)
  2. 重点理解
    • Promise 构造函数中 resolve、reject
    • then 方法
    • resolvePromise
// 定义三种状态
const REJECTED = "REJECTED";
const FULFILLED = "FULFILLED";
const PENDING = "PENDING";

/**
 * 1. then方法中的回调函数执行后
 * 2. 调用 resolve 还是 reject
 */
const resolvePromise = (promise2, x) => {
  const { resolve, reject } = promise2;
  // 这里的判断是可能自己的 Promise 要和别人的 Promise 混用,可能不同的 Promise 库之间相互调用
  // 如果 new 出来的 Promise2 和 x 是同一个,那么 x 永远不能成功或者失败,所以卡死了,直接报错
  if (promise2 === x)
    return reject(
      new TypeError("Chaining cycle detected for promise #<Promise>")
    );
  if ((typeof x === "object" && x !== null) || typeof x === "function") {
    let called;
    try {
      const then = x.then;
      if (typeof then !== "function") resolve(x);
      else {
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y);
          },
          (f) => {
            if (called) return;
            called = true;
            reject(f);
          }
        );
      }
    } catch (err) {
      if (called) return;
      called = true;
      reject(err);
    }
  } else {
    resolve(x);
  }
};

class Promise {
  constructor(executor) {
    this.status = PENDING; // Promise 的状态
    this.value = undefined; // 成功后的值
    this.reason = undefined; // 失败后的值

    // 成功回调函数,发布订阅
    this.onResolvedCallbacks = [];
    // 失败回调函数,发布订阅
    this.onRejectedCallbacks = [];

    /**
     * 1. 状态修改: PENDING -> FULFILLED
     * 2. 保存 resolve / return 的 value
     */
    const resolve = (value) => {
      if (value instanceof Promise) {
        return value.then(resolve, reject);
      }
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;
        this.onResolvedCallbacks.forEach((fn) => fn());
      }
    };

    /**
     * 1. 状态修改: PENDING -> REJECTED
     * 2. 保存 reject / return 的 value
     */
    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;
        this.onRejectedCallbacks.forEach((fn) => fn());
      }
    };

    try {
      // 执行传入函数
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled =
      typeof onFulfilled === "function" ? onFulfilled : (val) => val;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (err) => {
            throw err;
          };

    const promise2 = new Promise((resolve, reject) => {
      if (this.status === FULFILLED) {
        asap(() => {
          promise2.resolve = resolve;
          promise2.reject = reject;
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x);
          } catch (err) {
            reject(err);
          }
        });
      }
      if (this.status === REJECTED) {
        asap(() => {
          promise2.resolve = resolve;
          promise2.reject = reject;
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x);
          } catch (err) {
            reject(err);
          }
        });
      }
      if (this.status === PENDING) {
        this.onResolvedCallbacks.push(() => {
          asap(() => {
            promise2.resolve = resolve;
            promise2.reject = reject;
            try {
              const x = onFulfilled(this.value);
              resolvePromise(promise2, x);
            } catch (err) {
              reject(err);
            }
          });
        });
        this.onRejectedCallbacks.push(() => {
          asap(() => {
            promise2.resolve = resolve;
            promise2.reject = reject;
            try {
              const x = onRejected(this.reason);
              resolvePromise(promise2, x);
            } catch (err) {
              reject(err);
            }
          });
        });
      }
    });

    return promise2;
  }

  catch(errCallback) {
    return this.then(null, errCallback);
  }
}

Promise 中方法作用

  1. resolve/reject:将 PENDING 状态修改 FULFILLED/REJECTED
  2. 根据 this.status 状态,向微任务队列添加微任务
  3. resolvePromise:then 中的回调函数执行完后,修改 pending 的状态

预备知识点

  1. 任务分为同步任务和异步任务;当同步任务执行完后,才执行异步任务
  2. 异步任务分为 微任务 和 宏任务;微任务在宏任务之前执行
  3. 常见的微任务:Promise(js 引擎)
  4. 宏任务:setTimeout(运行环境:浏览器、node)

题目

// 下面值的输出顺序
console.log(1);
const promise = new Promise((resolve) => {
  console.log(2);
  resolve(3);
  console.log(4);
});

console.log(5);
promise
  .then((data) => {
    console.log(data);
    setTimeout(() => console.log(6), 0);
    return 7;
  })
  .then((data) => {
    console.log(data);
  });

setTimeout(() => console.log(8), 0);
console.log(9);

// 答案:1,2,4,5,9,3,7,8,6

解析

  1. 输出 1(先执行同步任务)
  2. 输出 2
    • 构造函数是同步任务;promise 构造函数执行了传进来的函数
  3. 输出 4
  4. 输出 5
  5. 输出 9
    1. 第一个 then 方法:
      • resolve(3)将 PENDING 改成 FULFILLED
      • 走 if(this.status === FULFILLED) 的判断,里面只有一个微任务执行函数(添加到微任务队列中)
    2. 第二个 then 方法:
      • 没有执行 resolve 或 reject 其中一个方法,此时还是 PENDING
      • 将 then 中的传入的方法添加到上一个 then 中的 onResolvedCallbacks 数组中
  6. 输出 3
    • resolve(3): 只是改变当前状态:pending -> fulfilled,this.value = 3
    • this.then(...)时,将执行 then 中的函数并传入 this.value 的值
    • setTimeout(...)放到宏任务队列中
    • 返回 7
  7. 输出 7
  8. 输出 8
    • 微任务队列执行完毕后,开始执行宏任务队列
    • 8 比 6 先进队列;根据队列特性:先进先出
  9. 输出 6

网上资料

  1. 以上讲解采用的 Promise 全部代码
  2. 谈谈 promise,谈谈微任务
  3. 手写 Promise