Promise A+ 规范和解读

131 阅读7分钟

原版的规范参考:promisesaplus.com/

规范的产生背景

该规范首次提出是在2012年左右,它是promise A 规范的升级版

在时间线上,Promise A+是早于ES6的

在那个时候,为了处理异步场景,JS中充斥着各种回调函数:

    // 以回调模式处理异步
    button.addEventListener("click", function(e){
      // 回调函数
    })

    setTimeout(function(){
      // 回调函数
    }, 1000)

以前,回调函数也不是唯一的处理异步的方法,有些异步场景也可以把函数保存到对象的属性中,以便调用:

    // 保存函数的属性
    xhr.onreadstatechange = function() {
    }
    button.onClick = function() {}

可以看出,过去处理异步的方式是不统一

在前端处理异步混乱的时代,Promise A+横空出世,制定了一个标准

重点提示:它不是一个技术,而是一套规范

它希望所有的前端开发者都遵循这套规则,使得所有的开发者的异步代码达到统一

后来,ES6在制作Promise 标准时,就参考了该规范

规范翻译

以下内容是翻译的规范内容,稍微抽象,看起来吃力的同学可以直接跳到----解读

一个Promise 对象应该能够表达一个异步操作,异步操作无论是成功还是失败,总会有一个结果,这个结果可以通过它的then方法进行交互,具体的交互方式是,注册一个回调函数到then方法中

该规范详细的描述了then方法的具体规格

Promise

promise 用于表示一个异步任务,它应该是一个带有 then 方法的对象

它有三种状态:pending 挂起,任务进行中fulfilled 完成rejected 失败

任何时刻,promise一定处于三种状态之一

ES6中的 Promise 完成状态是 resolved,单词不同,含义相同
  1. 当 promise 处于 pending 状态时:

    它可以在任何合适的时候把状态转变为 fulfilledrejected

  2. 当 promise 处于 fulfilled 状态时:

    它无法再次更改到其他状态

    它必须拥有一个值,表示任务完成时的数据,该数据是任何JS数据(甚至可以是undefined),并且这个值一旦确定下来,不可更改

  3. 当 promise 处于 rejected 状态时:

    它无法再次更改到其他状态

    它必须拥有一个值,表示任务失败的原因,该数据是任何JS数据(甚至可以是undefined),并且这个值一旦确定下来,不可更改

then 方法

promise 应该提供一个 then方法,通过这个方法,我们可以访问到任务完成时的值 或 任务失败的原因

then 方法可以接收两个参数:

promise.then(onFulfilled, onRejected)
  1. onFulfilledonRejected 都是可选参数:

    如果 onFulfilledonRejected 不是一个函数,则就必须被忽略

  2. 如果 onFulfilled 是一个函数:

    它应该在 promise 到达 fulfilled 状态时被调用,调用该函数时,应该把任务完成时的值作为第一个参数传递进去

    该函数只能被调用一次

  3. 如果 onRejected 是一个函数:

    它应该在 promise 到达 rejected 状态时被调用,调用该函数时,应该把任务失败的原因作为第一个参数传递进去

    该函数只能被调用一次

  4. onFulfilledonRejected 必须要等到当前执行栈清空后才能被调用,换句话说,它们是异步执行的

  5. 可以多次对同一个 promise 调用then方法,从而注册多个onFulfilledonRejected 当任务完成 或 任务失败时,将按照注册顺序,依次调用注册的 onFulfilledonRejected

  6. then 方法必须再次返回一个promise

    promise2 = promise1.then(onFulfilled, onRejected)
    

    在执行onFulfilledonRejected时,如果执行的过程中报了一个错误,则会导致promise2进入rejected 状态,并且传递和状态相关的失败原因,就是抛出错误

    在执行onFulfilledonRejected时,如果执行的过程中没有发生错误,并且返回值是x(x可以是任何数据类型,包括undefined),则会进入 任务完成处理流程 [[Resolve]] (promise2, x),具体流程在 下一小节 中解释

    如果onFulfilled不是一个函数,同时promise1已经fulfilledpromise2也会自动变成fulfilled,完成的相关数据和promise1一致

    如果onRejected不是一个函数,同时promise1已经rejectedpromise2也会自动变成rejected,失败的原因和promise1一致

任务完成处理流程

这个流程是一个概念上的操作规范,而不是真实的代码

Promise A+期望实现该规范的代码,也要按照这个处理流程去实现

为了便于说明,Promise A+ 把这个操作规范记作[[Resolve]](promise, x),它应该按照以下的流程进行:

  1. 如果promisex是同一个对象,则把promise设置为rejected状态,失败原因是一个TypeError

  2. 如果x是一个promise

    1. 如果x处于pendingpromise也必须处于pending,知道x完成或失败
    2. 如果x完成,promise也会完成,数据和x完成的数据一致
    3. 如果x失败,promise也会失败,数据和x失败原因一致
  3. 如果x是一个对象或函数:

    1. 读取x.then,如果读取发生异常,则直接让promise失败,失败原因就是抛出的错误,然后结束处理即可

    2. 如果x.then是一个function

      1. 调用x.then方法:x.then(onFulfilled, onRejected)
      2. onFulfilled执行时,如果得到的数据是y,则执行[[Resolve]](promise, y)
      3. onRejected执行时,如果得到的失败原因是error,则让promise变成失败状态,原因是error
    3. 如果x.then不是一个function, 把promise变成fulfilled状态,数据为x

    4. 如果调用x.then的时候发生错误,则把promise变成失败状态,失败原因为抛出的错误

  4. 如果x不是一个对象或函数,把promise变成fulfilled状态,数据为x

解读

如果对上面内容本身就理解的很透彻,下面内容就可以不必阅读了

1. ES6的Promise 和 Promise A+是一个什么样的关系?

ES6 的 Promise 遵循的是 Promise A+ 的规范

这也就意味着ES6的 Promise 完全可以和其他遵循了 Promise A+ 规范的 Promise 进行相互操作

比如,ES6的 Promise 可以和 JQuery 的Promise进行互操作

$.ajax(...).then(resp=>{
    // 这是JQuery的Promise的then函数
  return new Promise(resolve=>{
      // 这是ES6的Promise     
  })
})

2. 关于Promise的catch函数

Promise A+ 规范中并没有规定需要提供catch,但ES6的Promise提供了这一函数,它是为了让开发者使用起来更加方便

这并不代表ES6的Promise打破了这一规范,因为绝大部分规范都是只规定了你至少有什么东西,并不关心你多了什么东西

3. Promise必须通过构造函数new Promise得到吗

Promise A+没有这样规定,它可以通过任何途径得到,只不过ES6的Promise是通过构造函数得到的

Promise A+规范,要求Promise必须是一个对象或是一个函数,该对象或函数提供了满足要求的then方法即可

比如,下面的代码得到的都是满足规范的Promise

const promise1 = {
  then(onFufilled, onRejected){
    // 满足规范的then函数
  }
}

const promise2 = function(a, b){return a+b}
promise2.then = function(onFufilled, onRejected){
  // 满足规范的then函数
}

4. 关于Promise的状态

Promise A+ 并没有要求Promise必须提供什么属性来得到状态,这些都是Promise的内部信息,你可以对外提供,也可以不提供

ES6的Promise提供了两个属性[[PromiseStatus]][[PromiseValue]],分别表示Promise的状态和状态的相关数据(成功的数据或失败的原因),但这两个属性均无法在外部访问

另外,Promise A+ 也没有对状态的名称做出强制要求,在规范文档中,Promise A+ 使用了pendingfufilledrejected,但这些名称仅仅是为了说明规范而产生的,在具体的实现中,你可以自行规定任何的名称来表示三种状态

ES6的Promise就使用了pendingresolvedrejected来表示三种状态,这并没有打破Promise A+规范