大白话讲解Promise(二)理解Promise规范

http://www.cnblogs.com/lvdabao/p/5320705.html @吕大豹 html

上一篇咱们讲解了ES6中Promise的用法,可是知道了用法还远远不够,做为一名专业的前端工程师,还必须通晓原理。因此,为了补全咱们关于Promise的知识树,有必要理解Promise/A+规范,理解了它你才能知道Promise内部是怎么回事,咱们ES6中的Promise是如何一路走来的。
 
网上关于Promise/A+的翻译文档不少,因此我就不翻译一次了,本篇的目的在于为文档增长一些标注,以帮助咱们更好的理解。翻译内容引用自: http://malcolmyu.github.io/malnote/2015/06/12/Promises-A-Plus/,部分我认为不太合适的有做修改。
 

术语


 

Promise

promise 是一个拥有 then 方法的对象或函数,其行为符合本规范;前端

thenable

是一个定义了 then 方法的对象或函数,文中译做“拥有 then 方法”;jquery

值(value)

指任何 JavaScript 的合法值(包括 undefined , thenable 和 promise);git

异常(exception)

是使用 throw 语句抛出的一个值。github

拒绝缘由(reason)

表示一个 promise 的拒绝缘由。面试

要求


 
Promise 的状态
一个 Promise 的当前状态必须为如下三种状态中的一种: 等待态(Pending)完成 态(Fulfilled)和完成 态(Rejected)

等待态(Pending)

处于等待态时,promise 需知足如下条件:算法

  • 能够迁移至完成态或拒绝态
 
完成态(Fulfilled)
处于完成态时,promise 需知足如下条件:
  • 不能迁移至其余任何状态
  • 必须拥有一个不可变的终值

拒绝态(Rejected)

处于拒绝态时,promise 需知足如下条件:promise

  • 不能迁移至其余任何状态
  • 必须拥有一个不可变的据因

这里的不可变指的是恒等(便可用 === 判断相等),而不是意味着更深层次的不可变(译者注: 盖指当 value 或 reason 不是基本值时,只要求其引用地址相等,但属性值可被更改)。浏览器

Then 方法

一个 promise 必须提供一个 then 方法以访问其当前值、终值和据因。前端工程师

promise 的  then 方法接受两个参数:
 
promise.then(onFulfilled, onRejected)
参数可选

onFulfilled 和 onRejected 都是可选参数。

  • 若是 onFulfilled 不是函数,其必须被忽略
  • 若是 onRejected 不是函数,其必须被忽略
注:若是咱们只想传onRejected而不想传onFulfilled,能够这么写:pormise.then(null, onRejected)

onFulfilled 特性

若是 onFulfilled 是函数:

  • 当 promise 执行结束后其必须被调用,其第一个参数为 promise 的终值
  • 在 promise 执行结束前其不可被调用
  • 其调用次数不可超过一次

onRejected 特性

若是 onRejected 是函数:

  • 当 promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的据因
  • 在 promise 被拒绝执行前其不可被调用
  • 其调用次数不可超过一次

调用时机

onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用 注1

调用要求

onFulfilled 和  onRejected 必须被做为函数调用(即没有  this 值) 注2
 
注:也就是说,咱们在promise中就别用this了。

屡次调用

then 方法能够被同一个 promise 调用屡次

  • 当 promise 成功执行时,全部 onFulfilled 需按照其注册顺序依次回调
  • 当 promise 被拒绝执行时,全部的 onRejected 需按照其注册顺序依次回调
注:这里解释了咱们能够链式调用,promise.then().then().then()

返回

then 方法必须返回一个  promise 对象  注3
promise2 = promise1.then(onFulfilled, onRejected);
 
注:这就是咱们可以进行链式调用的缘由,由于then方法返回的仍是一个promise对象。
 
若是  onFulfilled 或者  onRejected 返回一个值  x ,则运行下面的  Promise 解决过程[[Resolve]](promise2, x)
  • 若是 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
  • 若是 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
  • 若是 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因

Promise 解决过程

Promise 解决过程 是一个抽象的操做,其需输入一个 promise 和一个值,咱们表示为 [[Resolve]](promise, x),若是 x 有then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;不然其用 x 的值来执行 promise 。

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

运行 [[Resolve]](promise, x) 需遵循如下步骤:

x 与 promise 相等

若是 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise

x 为 Promise

若是 x 为 Promise ,则使 promise 接受 x 的状态 注4

  • 若是 x 处于等待态, promise 需保持为等待态直至 x 被执行或拒绝
  • 若是 x 处于完成态,用相同的值执行 promise
  • 若是 x 处于拒绝态,用相同的据因拒绝 promise
注:这里就是解释咱们链式调用then时,能够继续进行异步操做,只要在onFulfilled中继续返回一个promise对象便可。例如:
runAsync1()
.then(function(data){
    console.log(data);
    return runAsync2(); //返回值为promise对象
})
.then(function(data){
    console.log(data);
    return runAsync3();
})

x 为对象或函数

若是 x 为对象或者函数:

  • 把 x.then 赋值给 then 注5
  • 若是取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
  • 若是 then 是函数,将 x 做为函数的做用域 this 调用之。传递两个回调函数做为参数,第一个参数叫作 resolvePromise ,第二个参数叫作 rejectPromise:
    • 若是 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
    • 若是 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
    • 若是 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了屡次,则优先采用首次调用并忽略剩下的调用
    • 若是调用 then 方法抛出了异常 e
      • 若是 resolvePromise 或 rejectPromise 已经被调用,则忽略之
      • 不然以 e 为据因拒绝 promise
    • 若是 then 不是函数,以 x 为参数执行 promise
  • 若是 x 不为对象或者函数,以 x 为参数执行 promise

若是一个 promise 被一个循环的 thenable 链中的对象解决,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的TypeError 为据因来拒绝 promise 注6

注释


  • 注1 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环以后的新执行栈中执行。这个事件队列能够采用“宏任务(macro-task)”机制或者“微任务(micro-task)”机制来实现。因为 promise 的实施代码自己就是平台代码(译者注: 即都是 JavaScript),故代码自身在处理在处理程序时可能已经包含一个任务调度队列或『跳板』)。

    译者注: 这里说起了 macrotask 和 microtask 两个概念,这表示异步任务的两种分类。在挂起任务时,JS 引擎会将全部任务按照类别分到这两个队列中,首先在 macrotask 的队列(这个队列也被叫作 task queue)中取出第一个任务,执行完毕后取出 microtask 队列中的全部任务顺序执行;以后再取 macrotask 任务,周而复始,直至两个队列的任务都取完。

    两个类别的具体分类以下:

    • macro-task: script(总体代码), setTimeoutsetIntervalsetImmediate, I/O, UI rendering
    • micro-task: process.nextTickPromises(这里指浏览器实现的原生 Promise), Object.observe,MutationObserver

      详见 stackoverflow 解答 或 这篇博客

  • 注2 也就是说在 严格模式(strict) 中,函数 this 的值为 undefined ;在非严格模式中其为全局对象。

  • 注3 代码实如今知足全部要求的状况下能够容许 promise2 === promise1 。每一个实现都要文档说明其是否容许以及在何种条件下容许 promise2 === promise1 。

  • 注4 整体来讲,若是 x 符合当前实现,咱们才认为它是真正的 promise 。这一规则容许那些特例实现接受符合已知要求的 Promises 状态。

  • 注5 这步咱们先是存储了一个指向 x.then 的引用,而后测试并调用该引用,以免屡次访问 x.then 属性。这种预防措施确保了该属性的一致性,由于其值可能在检索调用时被改变。

  • 注6 实现不该该对  thenable 链的深度设限,并假定超出本限制的递归就是无限循环。只有真正的循环递归才应能致使  TypeError 异常;若是一条无限长的链上  thenable 均不相同,那么递归下去永远是正确的行为。
 
补充:Promise/A+并未规定all和race方法,也就是说这两个方法是ES6本身增长的了。由于Promise/A+只是规范,ES6是作了本身的实现,固然能够本身加了。实现Promise规范的库有不少,好比jquery、dojo等,jquery在实现的时候还增长了更多的方法,咱们在下一篇会作讲解。网上也有很多朋友本身实现过Promise/A+,列出来供你们参考:
 
对于规范,有些同窗不太想看,我平时在面试的时候问起一些规范相关的问题,大多数面试者都回答不来。有些人或许会说,做为司机会开车不就好了,难道要知道汽车是怎么造的吗?那我这里想反问一下,你准备当一生司机吗?对于规范能够不那么充分研究,可是起码得知道关键部分,有这样一个意识,对于之后本身成长为大牛也有所帮助。
相关文章
相关标签/搜索