捋捋Promise

本文原创:duxiaoxuehtml

一些引用

一旦一个 Promise 决议,不管是如今仍是未来,下一个步骤老是相同的。git

既然 Promise 是经过 new Promise(..) 语法建立的,那你可能就认为能够经过 p instanceof Promise 来检查。但遗憾的是,这并不足以做为检查方法。识别 Promise(或者行为相似于 Promise 的东西)就是定义某种称为 thenable 的东西,将其定义为任何具备 then(..) 方法的对象和函数。咱们认为,任何这样的值就是 Promise 一致的 thenable。es6

Promise 模式构建的可能最重要的特性:信任。github

即便是当即完成的 Promise(相似于 new Promise(function(resolve){ resolve(42); }))也没法被同步观察到。也就是说,对一个 Promise 调用 then(..) 的时候,即便这个 Promise 已经决议,提供给 then(..) 的回调也总会被异步调用。编程

一个 Promise 决议后,这个 Promise 上全部的经过 then(..) 注册的回调都会在下一个异步时机点上依次被当即调用。这些回调中的任意一个都没法影响或延误对其余回调的调用。promise

-- YDKJS安全

先说异步

所谓"异步",简单说就是一个任务不是连续完成的,能够理解成该任务被人为分红两段,先执行第一段,而后转而执行其余任务,等作好了准备,再回过头执行第二段。 ... 相应地,连续的执行就叫作同步。因为是连续执行,不能插入其余任务,因此操做系统从硬盘读取文件的这段时间,程序只能干等着。异步

--阮一峰《ES6入门》异步编程

在 Promise 以前:回调函数

setTimeout(() => {
    // statements
}, 1000)
复制代码
var xhr = new XMLHttpRequest(),
    method = "GET",
    url = "https://developer.mozilla.org/";

xhr.open(method, url, true);
xhr.onreadystatechange = function () {
    if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
        console.log(xhr.responseText);
    }
};
xhr.send();
复制代码

缺点:函数

  • 大量的嵌套致使的回调地狱让人难以理解代码的实际发生顺序
  • 控制反转产生信任问题,回调执行状况没法保证

大脑对于事情的计划方式是线性的、阻塞的、单线程的语义,可是回调表达异步流程的方式是非线性的、非顺序的,这使得正确推导这样的代码难度很大。 回调函数的调用控制交与第三方函数内部,没法保证回调函数必定会被正确调用。可能出现不少异常状况:所需参数传递错误、调用回调过早或过晚、调用回调次数太多或太少、吞掉可能出现的错误与异常等等。 回调函数是 JavaScript 异步的基本单元。可是随着 JavaScript 愈来愈成熟,对于异步编程领域的发展,回调已经不够用了。 ... 咱们须要一种更同步、更顺序、更阻塞的的方式来表达异步,就像咱们的大脑同样。 也须要一个通用方案来解决信任问题。

-- YDKJS

Promise登场

promise的思想

若是咱们不把本身程序的 continuation 传给第三方,而是但愿第三方给咱们提供了解其任务什么时候结束的能力,而后由咱们本身的代码来决定下一步作什么。这种范式就称为Promise

Promise 决议后就是外部不可变的值,咱们能够安全地把这个值传递给第三方,并确信它不会被有意无心地修改。特别是对于多方查看同一个 Promise 决议的状况,尤为如此。一方不可能影响另外一方对 Promise 决议的观察结果。 不可变性听起来彷佛一个学术话题,但实际上这是 Promise 设计中最基础和最重要的因素。

Promise 是一种封装和组合将来值的易于复用的机制。

-- YKDJS

社区推进

最先的Promise是由社区首先提出和实现的,早期比较有名的有jQuery的Deferred对象、bluebird、Q等等。ES6也将Promise归入语言标准,提供了原生的Promise对象。

最新的规范是在2014年发布的promise/A+

Promise/A+规范

一个promise指示一个异步操做的最终结果。与promise交互的主要方法是经过它的then方法,在then方法中注册了接收promise的最终值或是没法被完成缘由的回调。

Promise/A+只专一于提供可操做的then方法的规范。

规范中指出,ECMAScript语言规范中的Promise对象基于本规范还实现了许多额外的要求,也就是说咱们能够自行实现一个彻底遵照promise/A+规范但没必要彻底遵照ECMAScript语言规范的Promise,某种程度上说ES6里面的Promise也只是许多promise/A+规范实现的一种。

术语

  • promise: 带有按规范实现的then方法的对象或函数
  • thenable: 定义了then方法的对象或函数
  • value: 任何合法的JavaScript值(包括undefinedthenable或是promise),终值
  • exception: 使用throw抛出的值
  • reason: 指示promise被拒缘由的值,拒因

规范要求

(一). Promise的状态

一个`promise`必须是这三种状态之一:

- `pending`(进行中、等待中)
- `fulfilled`(被完成、被执行)
- `rejected`(被拒绝)

状态的迁移:

1. 当`pending`时,`promise`的状态能够变到`fulfilled`或是`rejected`
2. 当`fulfilled`时,`promise`的状态不可再变,同时必须持有一个**不可变**的`value`(终值)
3. 当`rejected`时,`promise`的状态不可再变,同时必须持有一个**不可变**的`reason`(拒因)

这里的**不可变**指的是恒等(便可用 `===` 判断相等),但不意味着更深层次的不可变(如非基本类型值时,只要求引用地址相等)。
复制代码

(二). then方法

一个`promise`必须提供一个`then`方法以访问其当前值、终值和据因。方法接受两个参数:
```js
promise.then(onFulfilled, onRejected)
```
1. `onFulfilled`、`onRejected`均为可选参数,若是不是函数类型,则必须被忽略
2. 若是`onFulfilled`是个函数:在promise被完成前不可被调用;在promise被完成后必须被调用,以promise的终值做为第一个参数;不能被屡次调用。
3. 若是`onRejected`是个函数:在promise被拒绝前不可被调用;在promise被拒绝后必须被调用,以promise的据因做为第一个参数;不能被屡次调用。
4. 调用时机

    保证`onFulfilled`、`onRejected`在`then`被调用的那轮事件循环以后的新执行栈中异步执行。

    这一点可使用宏任务(macro-task)机制如`setTimeout`、`setImmediate`,或微任务(micro-task)机制如`MutationObserver`、`process.nextTick`来实现。
5. 调用要求

    `onFulfilled`和`onRejected`必须被做为函数调用(即没有 this 值)(在严格模式中,函数`this`的值为`undefined`;在非严格模式中其为全局对象)
6. `then`方法能够被同一个promise调用屡次

    - 当promise被完成时,全部`onFulfilled`按注册顺序依次回调
    - 当promise被拒绝时,全部`onRejected`按注册顺序依次回调

7. `then`方法必须返回一个promise对象

    ```js
    promise2 = promise1.then(onFulfilled, onRejected);
    ```
    - 若是`onFulfilled`、`onRejected`返回一个值x,则运行下面决议`promise`的过程:`[[Resolve]](promise2, x)`
    - 若是`onFulfilled`、`onRejected`抛出一个异常e,则`promise2`必须拒绝执行,并返回拒因e
    - 若是`onFulfilled`不是函数且`promise1`成功执行, `promise2`必须成功执行并返回相同的值
    - 若是`onRejected`不是函数且`promise1`拒绝执行,`promise2`必须拒绝执行并返回相同的拒因
复制代码

(三). 决议promise的过程(即[[Resolve]](promise, x)的具体实现)

1. 若是`x`与`promise`相等,以`TypeError`为拒因拒绝执行promise
2. 若是`x`为`Promise`的实例,则使`promise`接受`x`的状态
    - 若是`x`进行中,`promise`也需保持进行中的状态直至`x`被完成或拒绝
    - 若是`x`被完成,用相同的值执行`promise`
    - 若是`x`被拒绝,用相同的据因拒绝`promise`
3. 若是`x`为函数或者对象
    1. 把`x.then`赋值给`then`
    2. 若是取`x.then`的值时抛出错误`e` ,则以`e`为据因拒绝`promise`
        1. 若是`then`是函数,将`x`做为函数的做用域`this`调用之。传递两个回调函数做为参数,第一个参数叫作`resolvePromise`,第二个参数叫作`rejectPromise`:

            - 若是`resolvePromise`以值`y`为参数被调用,则运行`[[Resolve]](promise, y)`
            - 若是`rejectPromise`以据因`r`为参数被调用,则以据因`r`拒绝`promise`
            - 若是`resolvePromise`和`rejectPromise`均被调用,或者被同一参数调用了屡次,则优先采用首次调用并忽略剩下的调用
            - 若是调用`then`方法抛出了异常`e`:
                - 若是`resolvePromise`或`rejectPromise`已经被调用,则忽略之
                - 不然以`e`为据因拒绝`promise`
        2. 若是`then`不是函数,以`x`为参数执行`promise`
    3. 若是`x`不为对象或者函数,以`x`为参数执行`promise`
复制代码

测试

若是按照规范自行实现Promise,可使用下面官方提供的工具监测是否符合规范。

练习题

掘金上能够找到一些不错的练习题,用来巩固知识再好不过了。

参考

相关文章
相关标签/搜索