promiseA+

Promise

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。

所谓Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

为什么Promise叫做Promise?

Promise在红宝书中被叫做期约,也可以被翻译为承诺。是因为它的状态在从最初始的待定状态(pending)落定(settled)为代表成功的兑现(fulfilled)状态,或者代表失败的(rejected)状态之后。任何其他操作都无法改变这个状态了。如果说状态还可以被改变,那就不符合Promise设计的规范,那么叫Promise的意义也就丢失了。

为什么Promise存在then方法?

then方法是为期约实例添加处理程序(个人理解即是定义的各种函数方法)的主要方法。它的作用是为 Promise 实例添加状态改变时的回调函数。它接收最多两个参数:onResolved 处理程序和 onRejected 处理程序。这两个参数都是可选的,如果提供的话, 则会在期约分别进入“兑现”和“拒绝”状态时执行。

then方法会将被resolve后的值或reject的值传递出来,该值会被保存到对象内部,不会向外部暴露。如以下代码所示:

// 加载图片
function loadImg(src) {
  const promise = new Promise((resolve, reject) => {
    const img = document.createElement("img");
    img.onload = () => {
      resolve(img);
    };
    img.onerror = () => {
      reject("图片加载失败");
    };
    img.src = src;
  });
  return promise;
}

const src = "logo_new.png";
const result = loadImg(src);

result
  .then((img) => {
    console.log("img.width", img.width);
    return img;
  })
  .then((img) => {
    console.log("img.height", img.height);
  })
  .catch(err => {
    console.log(err);
  });

为什么Promise的then方法带有链式调用?

还是拿上述代码做举例,本来在第一个then方法可能分位多个职责角色来完成,把这些角色都分开,然后用一个链串起来。这样就将请求者和处理 者、包括多个处理者之间进行了分离。这也是设计模式中的职责链模式。多次使用.then方法时都会返回一个新的Promise,因此才可以实现被链式调用。

而前端中,最常见的就是链式操作。例如jQuery的链式操作和Promise的链式操作。

$('.div1')
.show()
.css('color', 'red')
.append($('#p1'))

异常处理语句

你可以用 throw 语句抛出一个异常并且用 try...catch 语句捕获处理它。

try...catch 语句

try...catch 语句标记一块待尝试的语句,并规定一个以上的响应应该有一个异常被抛出。如果我们抛出一个异常,try...catch语句就捕获它。

try...catch 语句有一个包含一条或者多条语句的 try 代码块,0 个或 1 个的catch代码块,catch 代码块中的语句会在 try 代码块中抛出异常时执行。

在我们后面的Promise实现中就会使用到对应try...catch语句对异常进行捕获。

throw 语句

使用throw语句抛出一个异常。当你抛出异常,你规定一个含有值的表达式要被抛出。

throw语句就可以用到在try...catch语句中,catch代码块中抛出异常时使用。

Promise/A+ 实现

Promise/A+

Promise/A+中文翻译版本

Promise/A+测试用例

Promise其实就是一个构造函数,我们可以使用这个构造函数创建一个Promise实例。 该构造函数很简单,它只有一个参数,按照Promise/A+规范的命名,我们把Promise构造函数的参数叫做executor,它是函数类型的参数。 这个函数又 具有resolvereject两个方法作为参数。

因此我们可以根据此结论开始实现Promise 的第一步,先简单地实现一个构造函数,代码如下。

function Promise(executor) {}

Promise初见雏形

在日常的嵌套回调场景中可以使用如下方法。

Request('userInfo')
    .then(
        data => Requeset(`${data.id}/friednList`),
        error => {
            console.log(error)
        }
    )
    .then(
        data => console.log(data),
        error => {
            console.log(error)
        }
    )

Promise构造函数返回一个Promise对象实例,这个返回的Promise对象具有一个then方法。在then方法中,调用者可以定义两个参数,分别是onfulfilledonrejected,他们都是函数类型的参数。其中,onfulfilled通过参数可以获取Promise对象经过resolve处理后的值,onrejected可以获取Promise对象经过reject处理后的值。通过这个值,我们来处理异步操作完成后的逻辑。

function Promise(exector)

Promise.prototype.then = function(onfufileed, onrejected) {}

接下来看一个示例,从示例中理解Promise的重点内容。

let promise1 = new Promise((resolve, reject) => {
    resolve('data')
})

promise1.then(data => {
    console.log(data)
})

let promise2 = new Promise((resolve, reject) => {
    reject('error')
})

promise2.then(data =>{
    console.log(data)
}, error => {
    console.log(error)
})

在使用 new 关键字调用 Promise 构造函数时,在合适的时机(往往是异步操作结束时)调用executor的参数resolve,并将经过resolve处理后的值作为resolve的函数参数执行,这个值便可以在后续then方法的第一个函数参数(onfulfilled)中拿到;同理,在出现错误时,调用exeuctor的参数reject,并将错误信息作为reject的函数参数执行,这个错误信息可以在后续then方法的第二个函数参数(onrejected)中得到。

因此,我们在实现Promise时,应该有两个变量,分别存储经过resolve处理后的值,以及经过reject处理后的值(当然,因为Promise状态的唯一性,不可能同时出现经过resolve处理后的值和经过reject处理后的值,因此也可以用一个变量来存储);同时还需要存在一个状态,这个状态就是Promise实例的状态(pending、fulfilled、rejected);最后要提供resolve方法及reject方法,这两个方法需要作为executor的参数提供给开发者使用,代码如下。

function Promise(executor) {
    // const self = this
    this.status = 'pending'
    this.value = undefined
    this.reason = undefined

    //function resolve(value){
     //   self.value = value
    //}

    //function reject(reason){
    //    self.reason = reason
    //}

    const resolve = value => {
        this.value = value
    }

    const reject = reason => {
        this.reason = reason
    }
}

Promise.prototype.then = function(onfulfilled = Function.prototype, onrejected = Function.prototype){
    onfulfilled(this.value)

    onrejected(this.reason)
}                                                        

为什么then要放在Promise构造函数的原型上,而不是放在构造函数内部呢?

这涉及了原型、原型链的知识:每个Promise实例的then方法逻辑都是一致的,实例在调用该方法时,可以通过原型(Promise.prototype)来调用,而不需要每次实例化都新创建一个then方法,以便节省内存。

Promise实现状态完善

接下来看一道题:

let promise = new Promise((resolve, reject) => {
    resolve('data')
    reject('error')
})

promise.then(data=> {
    console.log(data)
}, error => {
    console.log(error)
})

以上代码只会输出data。我们知道, Promise 实例的状态只能从 pending 变为 fulfilled,或者从pending 变为 rejected。状态一旦变更完毕,就不可再次变化或逆转。也就是说,如果一旦变为fulfilled,就不能再变为rejected;一旦变为rejected,就不能再变为fulfilled。

那我们的代码实现显然无法满足这一特性。执行上一段代码将会输出data及error,因此需要对状态进行判断和完善,如下。

function Promise(executor) {
  this.status = 'pending'
  this.value = undefined
  this.reason = undefined

  const resolve = value => {
    if (this.status === 'pending') {
      this.value = value
      this.status = 'fulfilled'
    }
  }

  const reject = reason => {
    if (this.status === 'pending') {
      this.reason = reason
      this.status = 'rejected'
    }
  }
}

Promise.prototype.then = function (onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error }

  if(this.status === 'fulfilled') {
    onfulfilled(this.value)
  }
  if(this.status === 'rejected') {
    onrejected(this.reason)
  }
}

可以看到,resolve和reject方法中加入了判断,只允许Promise实例状态从pending变为fulfilled,或者从pending变为rejected。

同时注意,这里对Promise.prototype.then的参数 onfulfilled 和 rejected 进行了判断,当实参不是函数类型时,就需要赋予默认函数值。这时的默认值不再是函数元 Function.prototype 了。下一段会介绍为什么要这么改。

但是Promise是用来解决异步问题的,而我们的代码全部都是同步执行的,似乎还差了更重要的逻辑。

Promise异步实现完善

到目前为止,实现还差了哪些内容呢?我们从下面的示例代码入手,逐步分析。

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('data')
  }, 2000)
})

promise.then(data => {
  console.log(data)
})

正常来讲,上述代码会在2s后输出data,但是现在,代码并没有输出任何信息。这是为什么呢?

原因很简单,因为我们的实现逻辑全是同步的。上述代码在实例化一个Promise的构造函数时,会在setTimeout逻辑中调用resolve,也就是说,2s后才会调用resolve方法,更改Promise实例状态。而结合我们的实现,then方法中的 onfulfilled 是同步执行的,它在执行时 this.status 仍然为 pending,并没有做到 “2s后再执行onfulfilled”。

我们似乎应该在合适的时间去调用onfulfilled方法,这个合适的时间应该是开发者调用 resolve 的时刻,那么我们先在状态 (status) 为 pending时把开发者传进来的 onfulfilled 方法存起来,再在 resolve 方法中执行即可。代码如下:

function Promise(executor) {
  this.status = 'pending'
  this.value = undefined
  this.reason = undefined
  this.onFulfilledFunc = Function.prototype
  this.onrejectedFunc = Function.prototype

  const resovle = value => {
    if(this.status === 'pending') {
      this.value = value
      this.status = 'fulfilled'

      this.onFulfilledFunc(this.value)
    }
  }

  const reject = reason => {
    if(this.status === 'pending') {
      this.reason = reason
      this.status = 'rejected'

      this.onRejectedFunc(this.reason)
    }
  }

  executor(resolve,reject)
}

Promise.prototype.then = function(onfulfilled, onrejected) {
  onfulfilled = typeof onfulfilled === 'function' ? onfulfilled : data => data
  onrejected = typeof onrejected === 'function' ? onrejected : error => { throw error }

  if(this.status === 'fulfilled') {
     onfulfilled(this.value)
  }
  if(this.status === 'rejected') {
    onrejected(this.reason)
  }
  if(this.status === 'pending') {
    this.onFulfilledFunc = onfulfilled
    this.onRejectedFunc = onrejected
  }
}

通过测试发现,我们实现的代码也可以支持异步执行了!同时,我们知道 Promise 是异步执行的!再来看一个例子,请判断一下代码的输出结果:

const promise = new Promise((resolve, reject) => {
  resolve('data')
})

promise.then(data => {
  console.log(data)
})
console.log(1)

正常话,这里会按照顺序先输出1,再输出data。

而我们实现的代码却没有考虑这种情况,实际先输出了data,再输出1,。因此,需要将resolve和reject的执行放到任务队列中。这里可以使用queueMicrotask中,保证异步执行。

const resolve = value => {
  if(value instanceof Promise) {
    return value.then(resolve, reject)
  }
  queueMicrotask(() => {
    if(this.status === 'pending') {
      this.value = value
      this.status = 'fulfilled'

      this.onFulfilledFunc(this.value)
    }
  })
}

const reject = reason => {
  queueMicrotask(() => {
    if (this.status === 'pending') {
      this.reason = reason
      this.status = 'rejected'

      this.onRejectedFunc(this.reason)
    }
  })
}

executor(resolve,reject)

这样一来,在执行到executor(resolve, reject) 时,也能保证在queueMicrotask中才执行Promise被决议后的任务,不会阻塞同步任务。

同时,我们在resolve方法中加入了对value值是否为一个Promise实例的判断语句。到目前为止,整个实现代码如下所示:

function Promise(executor) {
  this.status = 'pending'
  this.value = null
  this.reason = null
  this.onFulfilledFunc = Function.prototype
  this.onRejectedFunc = Function.prototype

  const resolve = value => {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }

    queueMicrotask(() => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'

        this.onFulfilledFunc(this.value)
      }
    })
  }

  const reject = reason => {
    queueMicrotask(() => {
      if (this.status === 'pending') {
        this.reason = reason
        this.status = 'rejected'

        this.onRejectedFunc(this.reason)
      }
    })
  }

  executor(resolve, reject)
}

Promise.prototype.then = function(onFulfilled, onRejected) {
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : data => data
  onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }

  if(this.status === 'fulfilled') {
    onFulfilled(this.value)
  }
  if(this.status === 'rejected') {
    onRejected(this.reason)
  }
  if(this.status === 'pending') {
    this.onFulfilledFunc = onFulfilled
    this.onRejectedFunc = onRejected
  }
}

下面的实现也会按照顺序,先输出1,再输出data

const promise = new Promise((resolve, reject) => {
  resolve('data')
})

promise.then(data => {
  console.log(data)
})

console.log(1)

Promise细节完善

到此为止,我们的Promise实现似乎越来越靠谱了,但是还有些细节需要完善。

比如,在Promise实例状态变更之前添加多个then方法。

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('data')
  }, 2000)
})

promise.then(data => {
  console.log(`1: ${data}`)
})

promise.then(data => {
  console.log(`2: ${data}`)
})

以上代码应该会得到以下输出

1: data
2: data

而我们的实现只会输出2: data,这是因为第二个then方法中的onFulfilledFunc会覆盖第一个then方法中的onFulfilledFunc。

这个问题也好解决,只需要将所有 then 方法中的 onFulfilled 储存到一个数组 onFulfilledArray 中,在当前Promise被决议时依次执行 onFulfilledArray 数组内的方法即可。对于onRejectFunc 同理,改动后的实现代码如下。

function Promise(executor) {
  this.status = 'pending'
  this.value = null
  this.reason = null
  this.onFulfilledArray = []
  this.onRejectedArray = []

  const resolve = value => {
    if(value instanceof Promise) {
      return value.then(resolve, reject)
    }
    if (this.staus === 'pending') {
      queueMicrotask(() => {
        this.value = value
        this.staus = 'fulfilled'

        this.onFulfilledArray.forEach(fn => fn(value))
      })

    }
  }

  const reject = reason => {
    if (this.status === 'pending') {
      queueMicrotask(() => {
        this.reason = reason
        this.status = 'rejected'

        this.onRejectedArray.forEach(fn => fn(reason))
      })

    }
  }

  executor(resolve, reject)
}

Promise.prototype.then = function (onFulfilled, onRejected) {
  onFulfilled = typeof onFulfiledd === 'function' ? onFulfilled : data => data
  onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }

  if(this.status === 'fulfilled') {
    onFulfilled(this.value)
  }
  
  if(this.status === 'rejected') {
    onRejected(this.reason)
  }
  
  if(this.status === 'pending') {
    this.onFulfilledArray.push(onFulfilled)
    this.onRejectedArray.push(onRejected)
  }
}

另外一个需要完善的细节是,在构造函数中如果出错,将会自动触发 Promise 实例状态变为 rejected,因此我们用try…catch块对 executor进行包裹:

try{
  executor(resolve, reject)
} catch(e) {
  reject(error)
}

到目前为止,我们已经初步实现了基本的Promise。得到一个好的实现结果固然重要,但是在实现过程中,我们也加深了对Promise的理解,得出下面一些重要的结论。

  • Promise的状态具有凝固性。
  • Promise可以在then方法第二个参数中进行错误处理
  • Promise实例可以添加多个then处理场景。

距离完整的实现越来越近了,接下来继续实现Promise then链式调用效果。

Promise then 的链式调用

在正式介绍此部分知识点前,我们先来看一道题目:请判断以下代码的输出结果。

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('lucas')
  }, 2000)
})

promise.then(data => {
  console.log(data)
  return `${data} next then`
})
.then(data => {
  console.log(data)
})

这段代码执行后,将会在2s后输出lucas,紧接着输出 lucas next then。

我们看到,Promise实例的then方法支持链式调用,输出经过resolve处理的值后,如果在then方法体的onfulfilled函数中同步显示返回新的值,则将会在新Promise实例then方法的onfulfilled函数体中输出新值。

如果在第一个then方法体的onfulfilled函数中返回另一个Promise实例,结果又将如何呢?

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('lucas')
  }, 1000)
})

promise.then(data => {
  console.log(data)
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${data} next then`)
    }, 1000)
  })
})
.then(data => {
  console.log(data)
})

上述代码将会在1s后输出 lucas 紧接着再过 1s (第2s)输出 lucas next then

由此可知,一个Promise实例then方法的onfulfilled函数和onrejected函数时支持再次返回一个Promise实例的,也支持返回一个非Promise实例的普通值,并且,返回的这个 Promise实例或这个非Promise实例的普通值将会传给下一个then方法的onfulfilled函数或onrejected函数,这样,then方法就支持链式调用了。

链式调用的初步实现

让我们分析一下,是不是为了能够支持 then 方法的链式调用,每一个then方法的 onfulfilled 函数和 onrejected 函数都应该返回一个Promise实例。

我们一步一步来分析,先看一个实际使用Promise链的场景,代码如下:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('lucas')
  }, 2000)
})

promise.then(data => {
  console.log(data)
  return `${data} next then`
})
.then(data => {
  console.log(data)
})

这种onfulfilled函数会返回一个普通字符串类型的基本值,这里的onfulfilled函数的代码如下:

data => {
  console.log(data)
  return `${data} next then`
}

在前面实现then方法中,我们可以创建一个新的Promise实例,即promise2,并最终将这个promise2返回,代码如下:

Promise.prototype.then = function(onfulfilled, onrejected){
  onFulfilled = typeof onFulfiledd === 'function' ? onFulfilled : data => data
  onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }

  // promise2 将作为 then 方法的返回值
  let promise2
  if(this.status === 'fulfilled'){
    return promise2 = new Promise((resolve, rejecet) => {
      setTimeout(() => {
        try {
          // 这个新的promise2 resolved的值为onfulfiledd的执行结果
          let result = onfulfilled(this.value)
          resolve(result)
        } catch(e){
          reject(e)
        }
      })
    })
  }

  if(this.status === 'rejected') {
    onrejected(this.reason)
  }
  if(this.status === 'pending') {
    this.onFulfilledArray.push(onfulfilled)
    this.onRejectedArray.push(onrejected)
  }
}

当然,别忘了另外两种情况也要加入相同的逻辑:

Promise.prototype.then = function(onfulfilled, onrejected){
  onFulfilled = typeof onFulfiledd === 'function' ? onFulfilled : data => data
  onRejected = typeof onRejected === 'function' ? onRejected : error => { throw error }

  // promise2 将作为 then 方法的返回值
  let promise2
  if(this.status === 'fulfilled'){
    return promise2 = new Promise((resolve, reject) => {
      queueMicroTask(() => {
        try {
          // 这个新的promise2 resolved 处理后的值为onfulfiledd的执行结果
          let result = onfulfilled(this.value)
          resolve(result)
        } catch(e){
          reject(e)
        }
      })
    })
  }

  if(this.status === 'rejected') {
    return promise2 = new Promise((resolve, reject) => {
      queueMicroTask(() => {
        try {
          // 这个新的promise2 reject 处理后的值为onrejected的执行结果
          let result = onrejected(this.value)
          resolve(result)
        } catch(e){
          reject(e)
        }
      })
    })
  }
  if(this.status === 'pending') {
    return promise2 = new Promise((resolve, reject) => {
      try {
        this.onFulfilledArray.push(() => {
          const result = onfulfilled(this.value)
          resolve(result)
        })
      } catch (e) {
        reject(e)
      }

      try {
      this.onRejectedArray.push(() => {
       const result = onrejected(this.reason)
       resolve(result)
      })
      } catch (e) {
        reject(e)
      }
    })
  }
}

这里要重点理解this.status === 'pending' 判断分支中的逻辑,这也是最难理解的。当使用Promise实例调用其then方法时,应该返回一个Promise实例,返回的就是this.status === 'pending' 判断分支中返回的promise2,。那么,这个promise2什么时候被决议呢?应该是在异步处理结束后,依次执行onFulfilledArrayonRejectedArray数组中的函数时。

我们再思考一下,onFulfilledArrayonRejectedArray数组中的函数应该做些什么呢?很明显,需要切换promise2的状态,并进行决议。

理顺了 onFulfilledArrayonRejectedArray 数组中的函数需要执行的逻辑,再进行改动。将this.onFulfilledArray.push的函数由:

this.onFulfilledArray.push(onfulfilled)

改为以下形式:

() => {
    try {
      let result = onfulfilled(this.value)
      resolve(result)
    }
    catch (e) {
      reject(e)
    }
}

this.onRejectedArray.push函数的改动方式同理。

此时Promise实现的完整代码如下:

function Promise(executor) {
  this.status = 'pending'
  this.value = null
  this.reason = null
  this.onFulfilledArray = []
  this.onRejectedArray = []

  const resolve = value => {
    if (value instanceof Promise) {
      return value.then(resolve, reject)
    }

    queueMicrotask(() => {
      if(this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'

        this.onFulfilledArray.forEach(fn => {
          fn(value)
        })
      }
    })
  }

  const reject = reason => {
    queueMicrotask(() => {
      if(this.status === 'pending') {
        this.reason = reason
        this.status = 'onrejected'

        this.onRejectedArray.forEach(fn => {
          fn(reason)
        })
      }
    })
  }

  try {
    executor(resolve ,reject)
  } catch(error) {
    reject(error)
  }
}

Promise.prototype.then = function(onfulfilled, onrejected) {
  // promise2 将作为 then方法的返回值
  let promise2
  if (this.status === 'fulfilled') {
    return promise2 = new Promise((resolve, reject) => {
      try {
        // 这个新的promise2得经过resolve处理后的值为onfulfilled的执行结果
        const result = onfulfilled(this.value)
        resolve(result)
      } catch(error) {
        reject(error)
      }
    })
  }

  if(this.status === 'onrejected') {
    return promise2 = new Promise((resolve, reject) => {
      try {
        // 这个新的promise2得经过reject处理后的值为onrejected的执行结果
        const result = onrejected(this.reason)
        resolve(result)
      } catch (error) {
        reject(error)
      }
    })
  }

  if(this.status === 'pending') {
    return promise2 = new Promise((resolve, reject) => {
      this.onFulfilledArray.push(() => {
        try {
          const result = onfulfilled(this.value)
          resolve(result)
        } catch(error) {
          reject(error)
        }
      })

      this.onRejectedArray.push(() => {
        try {
          const result = onrejected(this.reason)
          resolve(result)
        } catch(error) {
          reject(error)
        }
      })
    })
  }
}

链式调用的完善实现

我们继续来实现then方法,以便显示返回一个Promise实例。对应场景下的实现代码如下:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('lucas')
  }, 2000)
})

promise.then(data => {
  console.log(data)
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${data} next then`)
    }, 2000)
  })
})
.then(data => {
  console.log(data)
})

基于第一种 onfulfilled 函数和 onrejected 函数返回一个普通值的情况,要实现这种 onfulfilled 函数和 onrejected 函数返回一个 Promise 实例的情况并不困难。但是需要小幅度重构一下代码,在之前实现的 let result = onfulfilled(this.value) 语句和 let result = onrejected(this.reason) 语句中,使变量 result 由一个普通值变为一个 Promise 实例。换句话说就是,变量 result 既可以是一个普通值,也可以是一个Promise实例,位次,我们抽象出 resolvePromise 方法进行统一处理。对已有实现进行改动后的代码如下:

const resolvePromise = (promise2, result, resolve, reject) => {

}

Promise.prototype.then = function (onfulfilled, onrejected) {
  // promise2 将作为 then 方法的返回值
  let promise2
  if (this.status === 'fulfilled') {
    return promise2 = new Promise((resolve, reject) => {
      try {
        // 这个新的 promise2 的经过 resolve 处理后的值为 onfulfilled 的执行结果
        const result = onfulfilled(this.value)
        resolvePromise(promise2, result, resolve, reject)
      } catch(error) {
        reject(error)
      }
    })
  }

  if (this.status === 'onrejected') {
    return promise2 = new Promise((resolve, reject) => {
      try {
        // 这个新的 promise2 的经过 resolve 处理后的值为 onfulfilled 的执行结果
        const result = onrejected(this.value)
        resolvePromise(promise2, result, resolve, reject)
      } catch(error) {
        reject(error)
      }
    })
  }

  if (this.status === 'pending') {
    return promise2 = new Promise((resolve, reject) => {
    this.onFulfilledArray.push(value => {
        try {
          const result = onfulfilled(value)
          resolvePromise(promise2, result, resolve, reject)
        } catch(error) {
          reject(error)
        }
      })

      this.onRejectedArray.push(reason => {
        try {
          const result = onrejected(reason)
          resolvePromise(promise2, result, resolve, reject)
        } catch(error) {
          reject(error)
        }
      })
    })
  }
}

现在的任务就是完成 resolvePromise 函数, 这个函数接收以下 4 个参数

  • promise2:返回的Promise实例
  • result:onfulfilled 或 onrejected 函数的返回值
  • resolve:Promise2 的 resolve 方法
  • reject: Promise2 的 reject 方法

有了这些参数,我们就具备了抽象逻辑的必备条件,接下来就是动手实现。

const resolvePromise = (promise2, result, resolve, reject) => {
  // 当 result 和 promise2 相等时, 也就是在onfulfilled返回promise2时,执行reject
  if (result === promise2) {
    return reject(new TypeError('error due to circular reference'))
  }

  // 是否已经执行过 onfulfilled 或 onrejected
  let consumed = false
  let thenable

  if (result instanceof Promise) {
    if (result.status === 'pending') {
      result.then(function(data) {
        resolvePromise(promise2, data, resolve, reject)
      }, reject)
    } else {
      result.then(resolve, reject)
    }
    return
  }

  let isComplexResult = target => (typeof target === 'function' || typeof target === 'object' && (target !== null))

  // 如果返回的是疑似Promise类型
  if (isComplexResult(result)) {
    try {
      thenable = result.then
      // 判断返回值是否是 Promise 类型
      if (typeof thenable === 'function') {
        thenable.call(result, function(data) {
          if (consumed) {
            return
          }
          consumed = true

          return resolvePromise(promise2, data, resolve, reject)
        }, function(error) {
          if (consumed) {
            return
          }
          consumed = true

          return reject(error)
        })
      } else {
        resolve(result)
      }
    } catch (error) {
      if (consumed) {
        return
      }
      consumed = true
      return reject(e)
    }
  } else {
    resolve(result)
  }
}

我们看到,resolvePromise 方法的第一步是对 “死循环” 进行处理,并在发生死循环时抛出错误,错误信息为 new TypeError(‘error due to circular reference’)

怎么理解这个处理呢? Promise实现规范中支出,其实出现 “死循环” 的情况如下所示:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('lucas')
  }, 200)
})

promise.then(onfulfilled = data => {
  console.log(data)
  return onfulfilled(data)
})
.then(data => {
  console.log(data)
})

接着,对于 onfulfilled 函数返回的结果 result;如果 result 不是 Promise 实例,不是对象,也不是函数,而是一个普通值的话(isComplexResult 函数用于对此进行判断),则直接对promise2进行决议。

对于 onfulfilled 函数返回的结果 result: 如果 result 含有 then 属性方法, 那么我们称该属性方法为 thenable, 说明result 是一个 Promise 实例,当执行该实例的 then 方法(既 thenable) 时,返回结果还可能是一个 Promise 实例类型,也可能是一个普通值,因此还要递归调用 resolvePromise。下面代码举例为什么要递归调用方法:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve('lucas')
  }, 200)
})

promise.then(data => {
  console.log(data)
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(`${data} next then`)
    }, 200)
  })
  .then(data => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(`${data} next then`)
      }, 200)
    })
  })
})
.then(data => {
  console.log(data)
})

以上代码会先输出 lucas,在10s时输出 lucas next then next then

此时,Promise实现的完整代码如下:

function Promise(executor) {
  this.status = 'pending'
  this.value = null
  this.reason = null
  this.onFulfilledArray = []
  this.onRejectedArray = []

  const resolve = value => {
    if(value instanceof Promise) {
      return value.then(resolve ,reject)
    }
    queueMicrotask(() => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'

        this.onFulfilledArray.forEach(fn => {
          fn(value)
        })
      }
    })
  }

  const reject = reason => {
    queueMicrotask(() => {
      if(this.status === 'pending') {
        this.reason = reason
        this.status = 'rejected'

        this.onRejectedArray.forEach(fn => {
          fn(reason)
        })
      }
    })
  }

  try {
    executor(resolve, reject)
  } catch (error) {
    reject(error)
  }
}

/*
resolvePromise函数即为根据result的值来决定promise2的状态的函数
也即标准中的[Promise Resolution Procedure](<https://promisesaplus.com/#point-47>)
result为`promise2 = promise1.then(onResolved, onRejected)`里`onResolved/onRejected`的返回值
`resolve`和`reject`实际上是`promise2`的`executor`的两个实参,因为很难挂在其它的地方,所以一并传进来。
*/
const resolvePromise = (promise2, result, resolve, reject) => {
	// 当 result 和 promise2 相等时,也就是onfulfilled返回promise2时,进行抛错
	if (result === promise2) {
		reject(new TypeError("error due to circular reference"));
	} // 是否已经执行过 onfulfilled 或 onrejected

	let consumed = false;
	let thenable;

	if (result instanceof PromiseFn) {
		// 如果result的状态还没有确定,那么它是有可能被一个thenable决定最终状态和值的
		// 所以这里需要做一下处理,而不能一概的以为它会被一个“正常”的值resolve
		if (result.status === "pending") {
			result.then(function (data) {
				resolvePromise(promise2, data, resolve, reject);
			}, reject);
		} else {
			// 但如果这个Promise的状态已经确定了,那么它肯定有一个“正常”的值,而不是一个thenable,所以这里直接取它的状态
			result.then(resolve, reject);
		}
		return;
	}

	let isComplexResult = (target) =>
		(typeof target === "function" || typeof target === "object") &&
		target !== null; // 如果返回的是疑似Promise类型

	if (isComplexResult(result)) {
		try {
			// 2.3.3.1 因为result.then有可能是一个getter,这种情况下多次读取就有可能产生副作用
			// 即要判断它的类型,又要调用它,这就是两次读取
			thenable = result.then; // 判断返回值是否是 PromiseFn 类型
			if (typeof thenable === "function") {
				thenable.call(
					result,
					function (data) {
						if (consumed) {
							return;
						}
						// 2.3.3.3.3 即这三处谁先执行就以谁的结果为准
						consumed = true;

						return resolvePromise(promise2, data, resolve, reject);
					},
					function (error) {
						if (consumed) {
							return;
						}
						// 2.3.3.3.3 即这三处谁先执行就以谁的结果为准
						consumed = true;

						return reject(error);
					}
				);
			} else {
				// 2.3.3.4
				resolve(result);
			}
		} catch (e) {
			if (consumed) {
				return;
			}
			// 2.3.3.3.3 即这三处谁先执行就以谁的结果为准
			consumed = true;
			return reject(e);
		}
	} else {
		resolve(result);
	}
};

PromiseFn.prototype.then = function (onfulfilled, onrejected) {
	onfulfilled =
		typeof onfulfilled === "function" ? onfulfilled : (data) => data;
	onrejected =
		typeof onrejected === "function"
			? onrejected
			: (error) => {
					throw error;
			  }; // promise2 将作为 then 方法的返回值

	let promise2;

	if (this.status === "fulfilled") {
		return (promise2 = new PromiseFn((resolve, reject) => {
			queueMicrotask(() => {
				try {
					// 这个新的 promise2 的经过 resolve 处理后的值为 onfulfilled 的执行结果
					const result = onfulfilled(this.value);
					resolvePromise(promise2, result, resolve, reject);
				} catch (error) {
					reject(error);
				}
			});
		}));
	}

	if (this.status === "rejected") {
		return (promise2 = new PromiseFn((resolve, reject) => {
			queueMicrotask(() => {
				try {
					// 这个新的 promise2 的经过 resolve 处理后的值为 onfulfilled 的执行结果
					const result = onrejected(this.reason);
					resolvePromise(promise2, result, resolve, reject);
				} catch (error) {
					reject(error);
				}
			});
		}));
	}

	if (this.status === "pending") {
		return (promise2 = new PromiseFn((resolve, reject) => {
			this.onFulfilledArray.push((value) => {
				try {
					const result = onfulfilled(value);
					resolvePromise(promise2, result, resolve, reject);
				} catch (error) {
					reject(error);
				}
			});

			this.onRejectedArray.push((reason) => {
				try {
					const result = onrejected(reason);
					resolvePromise(promise2, result, resolve, reject);
				} catch (error) {
					reject(error);
				}
			});
		}));
	}
};