Promise异步原理分析

324 阅读3分钟

背景

看到一个Promise的题目:

new Promise((resolve, reject) => {
  console.log('promise1')
  resolve()
})
  .then(() => {
    console.log('then11')
    new Promise((resolve, reject) => {
      console.log('promise2')
      resolve()
    })
      .then(() => {
        console.log('then21')
      })
      .then(() => {
        console.log('then23')
      })
  })
  .then(() => {
    console.log('then12')
  })

本以为是输出顺序为: promise1, then11, promise2, then21, then23, then12, 实际却有些偏差: promise1, then11, promise2, then21, then12, then23 。有些出乎认知,于是乎找了es6-promise了解了解其实现的原理。

Promise基本概念介绍

  1. Promise状态:有pending、fulfilled(resolved个人习惯叫这个)、rejected三种状态,初始为pending。
  2. Promise用法:如上支持链式调用,异常可以通过 .catch 获取 .then的第二个回调捕获,还支持一些finally, race等操作
  3. Promise解决的问题:我理解主要是解决异步回调,异步代码异常捕获问题。

实现过程

Promise的实现 Promise支持链式调用,每一次调用都会新初始一个Promise,然后其onFulfill或者onRjection的回调函数的返回值都将在新返回的Promise中的result属性中存储。 也就是说明有多少个链式调用,就会出现对应次数的Promise实例数:

new Promise(resolve => {
  resolve()
}).then(function() {
  return 1
}).then(function(){
  return 2
})

对应的Promise链为:

rootPromise (new Promise生成的实例) -> child1Promise(第一个then) -> child2Promise(第二个then产生的)

每一个Promise都会记录其then或者catch方法传入的参数,通过微任务形式触发。

回到文档开头的例子, 将then中的 onFulfill 回调添加名称:

new Promise((resolve, reject) => {  // 该实例命名为 A1 callback中会有 then1
  console.log('promise1')
  resolve()
})
  // 这个then命名 A1then
  .then(function then1(){  // 新返回的一个空的Promise实例 A11,callback中会有 then12
    console.log('then11')
    new Promise((resolve, reject) => { // 该实例命名为B1 callback 会存在 then21
      console.log('promise2')
      resolve()
    })
      // B1then
      .then(function then21() { // 新返回的一个空的Promise实例B12,callback中会有 then23
        console.log('then21')
      })
      // B12then
      .then(function then23() {
        console.log('then23')
      })
  })
  // A11then
  .then(function then12() {
    console.log('then12')
  })
  1. 首先执行 A1 对应的代码,执行A1then方法时,发现 A1 已经 resovle 了,then1 函数被放在微任务队列中,等待执行,注意此时A11then只是被添加到A11的callback中,并不会被放到微任务队列中。
  2. 开始执行微任务,执行 then1 函数,执行过程中会同步创建一个 B1 的实例,同时也被resolve了,执行其 then 方法,会将其回调 then21放到微任务队列中,此时其第二个then(B12then)并不会被放到微任务队列中。接下来执行 A11then, 将then12也放入微任务队列中。
  3. 执行then21,修改对应Promise的状态 然后将 then23放入微任务队列中
  4. 执行 then12
  5. 执行 then23

最终的输出为 promise1, then11, promise2, then21, then12, then23;

Promise的链式调用,和我们常规的同步链式调用会不一样,不是一次性执行完,而是一个递归的过程,每次都会根据当前Promise的状态,决定是否需要将其callback放入到微任务队列中。

关于放入微任务队列,并不是一个主动行为,但可以将callback作为processs.nextTick(Node中的形式,浏览器可以通过MutationObserver方式)的回调函数,间接达到目的。

总结

通过看 es6-promise 的源码大致了解Promise的运行机制,里面也有很多不同的场景的适配,看得过程也建议手写些代码,增强对代码的理解。

Promise的运行机制需要结合promise状态和回调(then/catch的回调)函数结合理解,首先会在promises实例中暂存回调,如果有Promise状态切换了,则将其回调添加到微任务队列中,等待被执行。