精读JS系列(10)Promise—参考Promise/A+规范解读Promise

1,173 阅读8分钟

前言:必要前提

阅读本文有一个很重要的前提—— 就是知道 Promise是怎么构造的,也可以说知道在构造Promise实例过程中究竟发生了什么,是理解Promise必不可缺的关键一步。

试着问下自己这几个问题:

  • Promise的接收的回调函数是怎么执行的?(MDN称之为executor
  • resolvereject是什么? 是谁提供的?
  • 返回的Promise是怎么确定状态的?

如果回答这些问题没有把握,可以参考:

  • MDN Promise
  • Promise简介
  • 其他博文:尽可能是 github上的,分析得头头是道、有理有据,但有些难理解就是了。

如果上面的问题都能很好的回答上来,那么就正式开始用Promise/A+规范解读Promise(虽说如此,还是要结合MDNtutorialpoint上的内容)。

开始吧。


预备:Promise基本构造(非规范)

注意

  • 这不是Promise/A+的一部分,但是知道它对于理解Promise规范的细节很有帮助。
  • 下面的内容我会在括号中引用,或是在伪代码中引用。

任何一个Promise都会有两个最关键的部分:

  • [[PromiseStatus]] : 表明操作执行后的状态。默认为pending ; 在Firefox浏览器中,该字段为state
  • [[PromiseValue]] :是保存在Promise中的结果,它可能是一个reason(拒绝原因,异常.etc); 或是一个value(成功操作后提供的结果)。

关于[[PromiseStatus]][[PromiseValue]]的设置:

  1. 在调用resolve函数时,如果提供了参数y; 那么[[PromiseValue]]则被设为y的值。此时[[PromiseStatus]]将切换为fulfilled (或resolved) ; 例如:resolve(y)
  2. 在调用reject函数时,如果提供了异常或一个值r, 那么[[PromiseValue]]将被设为r。它可能是一个异常(Exception)。此时[[PromiseStatus]]将切换为rejected。 例如:reject(r)throw r

一旦[[PromiseStatus]]被设为fulfilledrejected二者任何一个状态,都可以说该Promise被settled

总结如下: 任何一个Promise实例prvaluereason都能表示为:

pr . [[PromiseValue]] // 它可能是value ,也可能是 reason。

任何一个Promise实例pr的状态都能表示为:

pr . [[PromiseStatus]] // pending / fulfilled / rejected 三者之一。


Promise 与 then方法

注意:

  • 参考来源: Promise/A+规范
  • 虽然是参考规范,但是并非是规范的翻译,小生也没有翻译规范的能力;下文大多是阅读规范后的理解。

Promise/A+规范规定,Promise必须为以下三种状态之一:

  1. pending : 操作还未确定结果。此时promise状态(即[[PromiseStatus]])可以切换到fulfilledrejected
  2. fulfilled:操作已经成功完成。无法再切换到其他状态。此时必须有一个确定的value即[[PromiseValue]]无法再更改
  3. rejected:操作已经失败。无法再切换到其他状态。此时必须有一个确定的reason即[[PromiseValue]]无法再更改

任何一个promise实例都必须有一个then方法;同样的,任何具有then方法的对象都可以称之为thenable对象。 如下所示:

promise.then(onFulfilled, onRejected)

  1. onFulfilledonRejected都是可选的,但是它们如果不是函数,都会被忽略。注意它们只能在promise被settled后调用并且只能被调用一次
  2. onFulfilled是一个函数时,promise的状态为fulfilled时被调用。并将promisevalue(即[[PromiseValue]])作为onFulfilled的第一个参数。
  3. onRejected是一个函数时,promise的状态为 rejected 状态时被调用,并且 promisereason(也是[[PromiseValue]]) 作为它的第一个参数。

注意

  • 只有在当前任务(即宏任务)完成后才能调用onFulfilledonRejected回调;如果当前宏任务未执行完毕,那么它们都不能被调用。 这保证了then异步的。
  • 一个then方法可以被同一个promise调用多次。

写一个伪代码:

 promise.then((value)=>{    // onFulfilled callback
     // [[PromiseStatus]] === fulfilled
     // [[PromiseValue]] === value 
     
 }, (reason)=>{          // onRejected callback
         // [[PromiseStatus]] === rejected
          // [[PromiseValue]] === reason
 })

then方法必须返回一个Promise实例 在知道怎么将返回值转换为promise前,必须知道[[Resolve]](有大佬译作promise解决程序,我就借用了)


Promise解决程序[[Resolve]]

[[Resolve]]是一个抽象操作,即:将x转换为Promise实例promise1的过程记作[[Resolve]](promise1,x)。这里x可以是任何类型的值,如undefinedprimitive data(原始值)、thenable对象等等。

伪代码如下:

x , promise1 // x 是任何值。

[[Resolve]](promise1, x)  // 执行Promise解决程序, x 和 promise作为参数

promise1 // 是根据值x转换得到的Promise实例值。

注意

  • 再次重申, thenable是拥有then方法的对象。
  • 为了防止混淆, 我会将Promise实例称作promise1,而不是规范中的promise
  • 过程我会排除掉一些少见情形,例如xpromise1是相同时,会抛出TypeError错误等等。必要时我会带上。
[[Resolve ]](promise1,x) 过程如下:

PA

  • 假定: x是一个Promise实例,那么:
  1. 如果x处于pending状态,那么promise1状态也将为pending; 直至x切换到fulfilledrejected状态为止。
  2. 如果x处于fulfilled状态,promise1也将切换到fulfilled状态,并将promise1的value(即promise1.[[PromiseValue]],下同)也设为x的value
  3. 如果x处于rejected状态,promise1也将切换到rejected状态,并将promise1的reason(也是promise1.[[PromiseValue]],下同)也设为x的reason

PB :

  • 假定: x不可能是Promise实例;
  • 假定: x是一个对象函数
  • 假设 thenx.then ;即then = x.then
  1. 若取属性x.then抛出异常er, 那么将promise1的reason设为er,并将promise1的状态(即promise1 . [[PromiseStatus]],下同)设为rejected
  2. 如果then 是一个函数,那么进入PC; 此时xthenable对象
  3. 如果then 不是一个函数 (注意:x也是一个对象或函数), 此时x不是thenable对象; 那么将promise1的状态设为fulfilled,并将promise1的value设为值x

PC

  • 此时x必须是一个thenable对象
  • 假定thenx.then
  • 假定 x对象定义如下:
  x = {
      ....
      then(resolvePromise, rejectPromise){  // resolvePromise, rejectPromise 是回调函数 
         ... 
         ...
      }
  }
  1. then方法的this指向x,并调用then方法; 注意then方法接收两个回调函数
  2. 如果在调用resolvePromise时提供了一个参数y, 即resolvePromise(y);那么执行[[Resolve]](promise1, y) ; 这里是一个递归
  3. 如果在调用rejectPromise时提供了一个参数r, 即rejectPromise(y) ; 那么将promise1的reason设为r;并且promise1的状态也会切换到rejected
  4. 如果resolvePromiserejectPromise都被数次调用,那么优先第一个调用,其余调用都将被忽略
  5. 如果then方法内部抛出一个异常e
    1. 如果resolvePromiserejectPromise已经被调用,那么忽略它。
    2. 否则,将promise1的reason设为e;并且将并且promise1的状态切换到rejected

PD

  • x不是Promise实例
  • x不是对象函数
  1. promise1的状态设为fulfilled,并且将promise1的value设为x
then返回值解析:

例如:

promise2 = promise1.then(onFulfilled, onRejected);

  1. onFulfilledonRejected 返回一个值x。 执行[[Resolve]](promise2, x)
  2. onFulfilledonRejected 如果抛出一个异常e。那么promise2状态切换为rejected,其reason设为e
  3. onFulfilledonRejected 都不是函数时,则:
    1. 如果promise1处于fulfilled状态,那么promise2也必须切换到fulfilled状态,并且将promise2的value设为promise1的value
    2. 如果promise1处于rejected状态,那么promise2也必须切换到rejected状态,并且将promise2的reason设为promise1的reason

okay.


验证

情形1: x 为 Promise实例
 var pr0 = Promise.reject('wrong,ha,ha,..')
 var pr1 = pr0.then(undefined, r=>{
     return Promise.reject('Madadesu')
 })
 console.log(pr1);  // pending
 setTimeout(console.log, 0,pr1)
 pr1.catch(()=>{});

输出:

在这里插入图片描述
参考 **规则 PA.3 **

情形2:x 是非thenable 的对象
  var pr0 = Promise.resolve({name:'Tadashi',age:30})
  console.log(pr0);

输出:

在这里插入图片描述
参考 规则PB.3

情形3:x 是一个 thenable对象
   var x = {
      name:'tadane',
      age:30,
      then(res,rej){
          res(this.name+' '+this.age)
          console.log(11111);	// 输出
          
          rej('tareru out!!')
          console.log(22222);   // 输出
          
          throw new Error('....')
          console.log(3333);        // 未执行
          
      }
  }
  var pr0 = Promise.resolve(x)
  setTimeout(console.log, 0, pr0)

输出:

在这里插入图片描述
参考规则PC.2

情形4:抛出异常
  var x = {
      name:'tadane',
      age:30,
      then(res,rej){
          throw new Error('bad js')
      }
  }
  var pr0 = Promise.resolve(x)
  setTimeout(console.log, 0, pr0)
  pr0.catch((e)=>{console.log(e.message);  })

仍然是 PC。其次,即使是在其他情形中,一旦抛出异常,promise也总是被设为rejected

在这里插入图片描述

情形5: x为原始值或undefined
  var p0 = Promise.reject('bad js')
  var p1 = p0.then(null, r=>{
      return 1111
  })
  setTimeout(console.log,0,p1)

输出

在这里插入图片描述
参考规则PD

常见情形就这么几个例子吧。 如果有其他的,可以自行验证

画个图

哎,我也搞个图。

在这里插入图片描述

最后

原本想带上catchfinally什么的;但发现算了。概念又都堆到一起去了。而且这些本来就没什么难度,所以就跳过吧。

下一篇,想搞一个async functionasync generator,这样衔接应该没什么问题吧……,入门时候都应该学过……吧。。javascript里面我感觉最有意思的部分就是生成器这里了。这里真的向其他地方借鉴了不少(虽然只是语法糖,并没有在底层提供支持),搞得我险些以为学的不是Javascript。

本文有点标题党,虽然是参考Promise/A+规范了,但也大改特改了一番,难免有失误,还请各位多多指正……