彻底弄明白Promise执行机制

180 阅读2分钟

准备

  1. 使用lie.js替换原生Promise,方便代码调试
  2. 理解MutationObserver观察器的使用,新建一个节点,每次触发,1,0循环更改text
  3. nextTick,先进先出,模拟微任务队列规则

Promise代码执行顺序分析

Promise.resolve()
    .then(
        // cb1
        () => {
            console.log('promise 1')
            Promise.resolve().then(
            // cb2
            () => {
                console.log('promise 2')
                Promise.resolve().then(
                // cb3
                () => {
                     console.log('promise 3')
                })
            })
        })
    .then(
        // cb4
        ()=> {
            console.log('promise 4')
        }
    )

结果:

promise 1
promise 2
promise 4
promise 3

上面代码执行顺序分析

  1. Promise.resolve()把第一个Promise的state改变为"FULFILLED"
  2. 此时代码有
Promise.prototype.then = function (onFulfilled, onRejected) {
  if (typeof onFulfilled !== 'function' && this.state === FULFILLED ||
    typeof onRejected !== 'function' && this.state === REJECTED) {
    return this;
  }
  var promise = new this.constructor(INTERNAL);
  if (this.state !== PENDING) {
    var resolver = this.state === FULFILLED ? onFulfilled : onRejected;
    unwrap(promise, resolver, this.outcome);
  } else {
    this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
  }

  return promise;
};
  1. 当state !== PENDING, 加入到unwrap,即微任务队列:queue = [],即第一个then的cb1加入里面去了
  2. 此时queue.push(task) === 1满足,draining=undefined !draining 满足,执行了scheduleDrain();
function immediate(task) {
  if (queue.push(task) === 1 && !draining) {
    scheduleDrain();
  }
}

即触发了一次文本节点的修改

  1. 返回了第一个then,新生成的Promise
  2. 执行第二个then,此时新生成Promise后,this.state !== PENDING 为false,走下面这个逻辑了
this.queue.push(new QueueItem(promise, onFulfilled, onRejected));
  1. 即把要执行的回调放入了当前Promise的queue里面
  2. 返回新的Promise
  3. 代码执行完毕,加入观察器里面的nextTick开始执行,draining = true了
  4. 采用先进先出的原则,执行queue的第一个回调函数
  5. 即打印了promise 1,有开始Promise.resolve().then,循环上面的内容
  6. 重复步骤3,执行unwrap,唯一不同的是,步骤4,draining = true了。只加入了task,没执行scheduleDrain,此时微任务队列queue有一个回调
  7. 返回新的Promise
  8. nextTick包裹了一层的, 开始执行handlers.resolve(promise, returnValue);
function unwrap(promise, func, value) {
  immediate(function () {
    var returnValue;
    try {
      returnValue = func(value);
    } catch (e) {
      return handlers.reject(promise, e);
    }
    if (returnValue === promise) {
      handlers.reject(promise, new TypeError('Cannot resolve promise with itself'));
    } else {
      // 没有返回值,执行这里
      handlers.resolve(promise, returnValue);
    }
  });
}
  1. resolve方法最终调用了以下方法,通过unwrap把回调加入到了微任务队列,此时微任务有两个回调
QueueItem.prototype.otherCallFulfilled = function (value) {
  unwrap(this.promise, this.onFulfilled, value);
};
  1. nextTick
function nextTick() {
  draining = true;
  var i, oldQueue;
  var len = queue.length;
  while (len) {
    oldQueue = queue;
    queue = [];
    i = -1;
    while (++i < len) {
      oldQueue[i]();
    }
    len = queue.length;
  }
  draining = false;
}
  1. 第一次的微任务队列执行到第二个while里面,当len = 2,又开始执行外部的while。
  2. 继续打印promise 2,promise 4,promise 3

总结:

  1. this.state !== PENDING,加入微任务队列,所以cb1 加入了
var queue = [cb1];
  1. promise 4加入到了上一个promise的队列里面
promise = {
    queue = [{
        onFulfilled: cb4
    }],
    state: ["PENDING"]
}
  1. 执行微任务队列中cb1,即打印promise 1,同理1,把cb2加入微任务队列
var queue = [cb2];
  1. 执行cb4之后,把它从promise的队列中加入到queue
var queue = [cb2, cb4];
  1. cb2出队列,执行cb2,打印promise2,同时cb3进队列
var queue = [cb4, cb3];
  1. 最后打印promise3