Promise系列2——Promises_A+ 规范解读

155 阅读6分钟

Promises/A+ 规范解读

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。如何判断一个对象是否是Promise对象,Promise提供了一个规范,凡是符合promise规范的函数和对象都可以称作为promise,其官方地址:promisesaplus.com/

本文是对官网提供的规范自己部分理解,并不是很全面

1. 术语

  • Promise: promise 是具有 then 行为符合本规范的方法的对象或函数。
  • thenable:一个定义了then 方法的对象和函数
  • value:包括任何 JavaScript 中合法的值(包括undefined,thenable 和 promise)
  • exception:使用 throw 抛出的一个值
  • reason:表示 promise 拒绝原因的一个值

由此可见 promise 的术语很少,也不难理解

2. 要求

2.1 promise 的状态

一个promise 必须处于以下三个状态之一:等待中(Pending)、已完成(Fulfilled)、已拒绝(Rejected);

  1. 处于等待中(Pending):可以转换为已完成(Fulfilled)或已拒绝(Rejected)状态
  2. 处于已完成(Fulfilled):不能够转换为别的状态,必须有一个不可变的值
  3. 处于已拒绝(Rejected):不能够转换为别的状态,必须有一个不可变的值来表示拒绝的原因

2.2 then 方法

一个promise 必须提供一个then方法去访问当前值和原因,在promise的then方法中接受两个参数

promise.then(onFulfilled, onRejected)
  • 两个参数都不是必须的并且都是函数,如果不是函数,需要忽略他们
  • 如果onFulfilled是一个函数
    • 他必须在promise完成之后调用,他的第一个参数是promise返回的结果
    • 不能在promise执行前调用
    • 不能够被调用多次,只能调用一次
  • 如果onRejected是一个函数
    • 他必须在promise被拒绝完成之后调用,他的第一个参数是promise拒绝返回的原因
    • 不能在promise被拒绝执行前调用
    • 不能够被调用多次,只能调用一次。
  • onFulfilled和onRejected 不能够在执行上下文堆栈中仅包含平台代码之前调用

提示:先看下面的代码,在JS事件循环中,先观察任务队列中有没有微任务,如果有执行所有的微任务,如果没有按照堆栈顺序执行任务列表中的宏任务。(知识点: js 中的宏任务和微任务)

关于这一条规范 更具体的可以参考官网 3.1

console.log('1'); # 执行上下文平台代码

setTimeout(function() {
  console.log('2');
}, 0); # 执行上下文中的宏任务

Promise.resolve().then(function() {
  console.log('3');
}); # 执行上下文中的微任务

console.log('4'); # 执行上下文平台代码

打印顺序:1 4 3 2
  • onFulfilled 和 onRejected 必须作为一个函数调用(里面没有this值)
  • then 方法可以被同一个promise调用多次
    • 如果promise状态是Fulfilled时,所有的 onFulfilled 方法按照注册的顺序执行
    • 如果promise状态是Rejected时,所有的 onRejected 方法按照注册的顺序执行
  • then 方法必须返回一个promise 对象
promise2 = promise1.then(onFulfilled, onRejected);
  • 只要 onFulfilled 或 onRejected 返回一个一个值 x, promise2 都会进入 onFulfilled
  • 如果 onFulfilled 或 onRejected 抛出一个异常e,则 promise2 必须是rejected状态,并返回拒绝原因 e
  • 如果 onFulfilled 不是一个函数,并且 promise1 的状态是已完成,则promise2 必须是已完成状态并且返回相同的值
  • 如果 onRejected 不是一个函数,并且 promise1 的状态是已拒绝,则promise2 必须是已拒绝状态并且返回相同拒绝的原因

我们写一段代码分别看一下上面的规范

# 1. 
const promise1 = Promise.reslve();
promise1.then(()=>{return "Lius"}).then(res=>{console.log(res)}) # Lius
const promise1 = Promise.reject();
promise1
  .then(null, () => {
    return "Lius";
  })
  .then(
    (res) => {
      console.log("Fulfilled", res); // # Lius
    },
    (err) => {
      console.log("Rejected", err);
    }
  ); 

# 2 不演示了

# 3 
const promise1 = Promise.reslve("Lius");
promise1
  .then(null, (err) => {
    console.log("promise1 reject", err);
  })
  .then((res) => {
    console.log("promise2 fulfilled",res); // Lius
  });

# 4
const promise1 = Promise.reject("Lius");
promise1
  .then((res) => {
    console.log("promise1 fulfilled", res);
  },null)
  .then((res) => {
    console.log("promise2 fulfilled", res); 
  },(err) => {
    console.log("promise2 reject", err); // Lius
  });

2.3 promise 的解析过程

Promise 的解析过程是一个抽象的操作,输入一个promise和值x,记作:[[Resolve]]``(promise, x) 如果 xthen 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise

这种 thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promise/A+ 协议的 then 方法即可;这同时也使遵循 Promise/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存。

表示 [[Resolve]](promise, x) 的过程如下

  • 如果 promise 和 x 指向同一个对象,则会抛出typeError拒绝promise
  • 如果 x 是一个 promise 对象
    • 如果 x 处于等待状态,promise需要保持等待状态直到状态被完成或拒绝
    • 如果 x 处于完成状态,使用相同的值完成promise
    • 如果 x 处于拒绝状态,使用相同的值拒绝promise
  • 如果 x 是一个对象或者函数
    • 先取 x.then 的值
    • 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
    • 如果 then 是一个函数,将 x 作为函数的this 作用域,传递两个参数 第一个参数是resolvePromise 第二个参数是rejectPromise
      • 如果resolvePromise以值y 被调用,则运行 [[Resolve]](promise, y)
      • 如果rejectPromise以值y 被调用,则运行 [[Reject]](promise, y)
      • 如果两者都被调用并且被调用多次,则优先采用首次调用结果,并忽略其他调用
// 以上三点使用代码表示一下
const promise = Promise.resolve({
  then: (resolveFun, rejectFun) => {
    resolveFun("XS") // 调用 rejectFun 会执行catch
  }
})

promise.then(res => {
  console.log("状态完成", res) // 状态完成 XS
}).catch(err => {
  console.log("状态拒绝", err)
})
  - 如果调用 then 方法时抛出了异常 e
     - 如果resolvePromise和rejectPromise已经被调用,则忽略异常
     - 否则以e 作为结果拒绝
const promise = Promise.resolve({
  then: (resolveFun, rejectFun) => {
    resolveFun("XS")
    rejectFun("error")
    throw new Error("抛出异常")
  }
})

promise.then(res => {
  console.log("状态完成", res)
}).catch(err => {
  console.log("状态拒绝", err)
})
// 如果在抛出异常之前指向resolveFun 或者 rejectFun,会执行到对应的方法回调,否则以抛出的异常拒绝promise
  • 如果 then 不为函数,以 x 为参数将 promise 变为已完成状态
  • 如果x 不是一个对象或者函数,以 x 为参数将 promise 变为已完成状态

3. 结束语

很艰难的把promise A+ 规范解读一遍,其中参考了网上很多文章,自己理解有90%左右,还是希望能够去看官网英文原版。 特别提示,红色部分我理解了很久还是没能够弄明白,见谅见谅,也希望又能够补充提示的 下一节,通过promise A+ 规范实现一个promise