本站已关停,现有内容仅作科研等非赢利用途使用。特此声明。
查看: 1514|回复: 0
打印 上一主题 下一主题

理解 Promise 的工作原理

[复制链接]
跳转到指定楼层
1#
发表于 2016-7-10 22:31:54 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
原文地址:https://blog.coding.net/blog/how-do-promises-work

> Javascript 采用回调函数(callback)来处理异步编程。从同步编程到异步回调编程有一个适应的过程,但是如果出现多层回调嵌套,也就是我们常说的厄运的回调金字塔(Pyramid of Doom),绝对是一种糟糕的编程体验。于是便有了 CommonJS 的 Promises/A 规范,用于解决回调金字塔问题。本文先介绍 Promises 相关规范,然后再通过解读一个迷你的 Promises 以加深理解。

### 什么是 Promise

一个 Promise 对象代表一个目前还不可用,但是在未来的某个时间点可以被解析的值。它允许你以一种同步的方式编写异步代码。例如,如果你想要使用 Promise API 异步调用一个远程的服务器,你需要创建一个代表数据将会在未来由 Web 服务返回的 Promise 对象。唯一的问题是目前数据还不可用。当请求完成并从服务器返回时数据将变为可用数据。在此期间,Promise 对象将扮演一个真实数据的代理角色。接下来,你可以在 Promise 对象上绑定一个回调函数,一旦真实数据变得可用这个回调函数将会被调用。

Promise 对象曾经以多种形式存在于许多语言中。

### 去除厄运的回调金字塔(Pyramid of Doom)

Javascript 中最常见的反模式做法是回调内部再嵌套回调。

    // 回调金字塔
    asyncOperation(function(data){
      // 处理 `data`
      anotherAsync(function(data2){
          // 处理 `data2`
          yetAnotherAsync(function(){
              // 完成
          });
      });
    });

引入 Promises 之后的代码

    promiseSomething()
    .then(function(data){
        // 处理 `data`
        return anotherAsync();
    })
    .then(function(data2){
        // 处理 `data2`
        return yetAnotherAsync();
    })
    .then(function(){
        // 完成
    });

Promises 将嵌套的 callback,改造成一系列的`.then`的连缀调用,去除了层层缩进的糟糕代码风格。Promises 不是一种解决具体问题的算法,而已一种更好的代码组织模式。接受新的组织模式同时,也逐渐以全新的视角来理解异步调用。

各个语言平台都有相应的 Promise 实现

- Java's java.util.concurrent.Future
- Python's Twisted deferreds and PEP-3148 futures
- F#'s Async<T>
- .Net's Task<T>
- C++ 11's std::future
- Dart's Future<T>
- Javascript's Promises/A/B/D/A+

下面我来相信了解一下 javascript 语言环境下各个规范的一些细节。

### Promises/A 规范

promise 表示一个最终值,该值由一个操作完成时返回。

* promise 有三种状态:**未完成** (unfulfilled),**完成** (fulfilled) 和**失败** (failed)。
* promise 的状态只能由**未完成**转换成`完成`,或者**未完成**转换成**失败** 。
* promise 的状态转换只发生一次。

promise 有一个 then 方法,then 方法可以接受 3 个函数作为参数。前两个函数对应 promise 的两种状态 fulfilled 和 rejected 的回调函数。第三个函数用于处理进度信息(对进度回调的支持是可选的)。

    promiseSomething().then(function(fulfilled){
            //当promise状态变成fulfilled时,调用此函数
        },function(rejected){
            //当promise状态变成rejected时,调用此函数
        },function(progress){
            //当返回进度信息时,调用此函数
        });

如果 promise 支持如下连个附加方法,称之为`可交互的 promise`

* get(propertyName)
    获得当前 promise 最终值上的一个属性,返回值是一个新的 promise。
* call(functionName, arg1, arg2, ...)
    调用当然 promise 最终值上的一个方法,返回值也是一个新的promise。

### Promises/B 规范

在 Promises/A 的基础上,Promises/B 定义了一组 promise 模块需要实现的 API

*`when(value, callback, errback_opt)`*
如果 value 不是一个 promise ,那么下一事件循环callback会被调用,value 作为 callback 的传入值。如果 value 是一个 promise,promise 的状态已经完成或者变成完成时,那么下一事件循环 callback 会被调用,resolve 的值会被传入 callback;promise 的状态已经失败或者变成失败时,那么下一事件循环 errback 会被调用,reason 会作为失败的理由传入 errback。

*`asap(value, callback, errback_opt)`*
与 when 最大的区别,如果 value 不是一个 promise,会被立即执行,不会等到下一事件循环。

*`enqueue(task Function)`*
尽可能快地在接下来的事件循环调用 task 方法。

*`get(object, name)`*
返回一个获得对象属性的 promise。

*`post(object, name, args)`*
返回一个调用对象方法的 promise。

*`put(object, name, value)`*
返回一个修改对象属性的 promise。

*`del(object, name)`*
返回一个删除对象属性的 promise。

*`makePromise(descriptor Object, fallback Function)`*
返回一个 promise 对象,该对象必须是一个可调用的函数,也可能是可被实例化的构造函数。

* 第一个参数接受一个描述对象,该对象结构如下,
```
    {
        "when": function(errback){...},
        "get": function(name){...},
        "put": function(name, value){...},
        "post": function(name, args){...},
        "del": function(name){...},
    }
```
上面每一个注册的 handle 都返回一个 resolved value或者 promise。

* 第二个参数接受一个 fallback(message,...args) 函数,当没有 promise 对象没有找到对应的 handle 时该函数会被触发,返回一个 resolved value 或者 promise。

*`defer()`*
返回一个对象,该对象包含一个 resolve(value) 方法和一个 promise 属性。
当 resolve(value) 方法被第一次调用时,promise 属性的状态变成 **完成**,所有之前或之后观察该 promise 的 promise 的状态都被转变成 **完成**。value 参数如果不是一个 promise ,会被包装成一个 promise 的 ref。resolve 方法会忽略之后的所有调用。

*`reject(reason String)`*
返回一个被标记为 **失败** 的 promise。
一个失败的 promise 上被调用 when(message) 方法时,会采用如下两种方法之一
1. 如果存在 errback,errback 会以 reason 作为参数被调用。when方法会将 errback 的返回值返回。
2. 如果不存在 errback,when 方法返回一个新的 reject 状态的promise 对象,以同一 reason 作为参数。

*`ref(value)`*
如果 value 是 promise 对象,返回 value 本身。否则,返回一个resolved 的 promise,携带如下 handle。
1. when(errback),忽略 errback,返回 resolved 值
2. get(name),返回 resolved 值的对应属性。
3. put(name, value) ,设置 resolved 值的对应属性。
4. del(name),删除 resolved 值的对应属性。
5. post(name, args), 调用 resolved 值的对应方法。
6. 其他所有的调用都返回一个 reject,并携带 "Promise does not handle NAME" 的理由。

*`isPromise(value) Boolean`*
判断一个对象是否是 promise

*`method(name String)`*
获得一个返回 name 对应方法的 promise。返回值是 "get", "put", "del" 和 "post" 对应的方法,但是会在下一事件循环返回。

### Promises/D 规范

为了增加不同 promise 实现之间的可互操作性,Promises/D 规范对promise 对象和 Promises/B 规范做了进一步的约定。以达到鸭子类型的效果(Duck-type Promise)。

简单来说Promises/D 规范,做了两件事情,

1. 如何判断一个对象是 Promise 类型。
2. 对 Promises/B 规范进行细节补充。

#### 甄别一个 Promise 对象

Promise 对象必须是实现 `promiseSend` 方法。
1. 在 promise 库上下文中,如果对象包含 `promiseSend` 方法就可以甄别为promise 对象
2. `promiseSend` 方法必须接受一个操作名称,作为第一个参数
3. 操作名称是一个可扩展的集合,下面是一些保留名称
    1. `when`,此时第三个参数必须是 rejection 回调。
        1. rejection回调必须接受一个 rejection 原因(可以是任何值)作为第一个参数
    2. `get`,此时第三个参数为属性名(字符串类型)
    3. `put`,此时第三个参数为属性名(字符串类型),第四个参数为新属性值。
    4. `del`,此时第三个参数为属性名
    5. `post`,此时第三个参数为方法的属性名,接下来的变参为方法的调用参数
    6. `isDef`
4. `promiseSend`方法的第二个参数为 resolver 方法
5. `promiseSend`方法可能接受变参
6. `promiseSend`方法必须返回`undefined`

#### 对 Promises/B 规范的补充

Promises/D 规范中对 Promises/B 规范中定义的ref、reject、def、defer方法做了进一步细致的约束,此处略去这些细节。

### Promises/A+ 规范

前面提到的 Promises/A/B/D 规范都是有CommonJS组织提出的,Promises/A+是有一个自称为[Promises/A+ 组织](https://github.com/promises-aplus)发布的,该规范是以Promises/A作为基础进行补充和修订,旨在提高promise实现之间的可互操作性。

Promises/A+ 对`.then`方法进行细致的补充,定义了细致的[Promise Resolution Procedure](https://promisesaplus.com/#point-45)流程,并且将`.then`方法作为promise的对象甄别方法。

此外,Promises/A+ 还提供了兼容性测试工具,以确定各个实现的兼容性。

### 实现一个迷你版本的Promise

上面扯了这么多规范,现在我们看看如何实现一个简单而短小的Promise。

#### 状态机

    var PENDING = 0;
    var FULFILLED = 1;
    var REJECTED = 2;

    function Promise() {
      // store state which can be PENDING, FULFILLED or REJECTED
      var state = PENDING;

      // store value or error once FULFILLED or REJECTED
      var value = null;

      // store sucess & failure handlers attached by calling .then or .done
      var handlers = [];
    }

#### 状态变迁

仅支持两种状态变迁,fulfill和reject

    // ...

    function Promise() {
        // ...

      function fulfill(result) {
        state = FULFILLED;
        value = result;
      }

      function reject(error) {
        state = REJECTED;
        value = error;
      }

    }

fulfill和reject方法较为底层,通常更高级的resolve方法开放给外部。

    // ...

    function Promise() {

      // ...

      function resolve(result) {
        try {
          var then = getThen(result);
          if (then) {
            doResolve(then.bind(result), resolve, reject)
            return
          }
          fulfill(result);
        } catch (e) {
          reject(e);
        }
      }
    }

resolve方法可以接受一个普通值或者另一个promise作为参数,如果接受一个promise作为参数,等待其完成。promise不允许被另一个promise fulfill,所以需要开放resolve方法。resolve方法依赖一些帮助方法定义如下:

    /**
     * Check if a value is a Promise and, if it is,
     * return the `then` method of that promise.
     *
     * @param {Promise|Any} value
     * @return {Function|Null}
     */
    function getThen(value) {
      var t = typeof value;
      if (value && (t === 'object' || t === 'function')) {
        var then = value.then;
        if (typeof then === 'function') {
          return then;
        }
      }
      return null;
    }

    /**
     * Take a potentially misbehaving resolver function and make sure
     * onFulfilled and onRejected are only called once.
     *
     * Makes no guarantees about asynchrony.
     *
     * @param {Function} fn A resolver function that may not be trusted
     * @param {Function} onFulfilled
     * @param {Function} onRejected
     */
    function doResolve(fn, onFulfilled, onRejected) {
      var done = false;
      try {
        fn(function (value) {
          if (done) return
          done = true
          onFulfilled(value)
        }, function (reason) {
          if (done) return
          done = true
          onRejected(reason)
        })
      } catch (ex) {
        if (done) return
        done = true
        onRejected(ex)
      }
    }

这里resolve和doResolve之间的递归很巧妙,用来处理promise的层层嵌套(promise的value是一个promise)。

### 构造器

    // ...

    function Promise(fn) {
        // ...
        doResolve(fn, resolve, reject);
    }


### .done方法

    // ...
    function Promise(fn) {
      // ...

      function handle(handler) {
        if (state === PENDING) {
          handlers.push(handler);
        } else {
          if (state === FULFILLED &&
            typeof handler.onFulfilled === 'function') {
            handler.onFulfilled(value);
          }
          if (state === REJECTED &&
            typeof handler.onRejected === 'function') {
            handler.onRejected(value);
          }
        }
      }

      this.done = function (onFulfilled, onRejected) {
        // ensure we are always asynchronous
        setTimeout(function () {
          handle({
            onFulfilled: onFulfilled,
            onRejected: onRejected
          });
        }, 0);
      }
      // ...
    }


### .then方法
    // ...
    function Promise(fn) {
        // ...
        this.then = function (onFulfilled, onRejected) {
          var self = this;
          return new Promise(function (resolve, reject) {
            return self.done(function (result) {
              if (typeof onFulfilled === 'function') {
                try {
                  return resolve(onFulfilled(result));
                } catch (ex) {
                  return reject(ex);
                }
              } else {
                return resolve(result);
              }
            }, function (error) {
              if (typeof onRejected === 'function') {
                try {
                  return resolve(onRejected(error));
                } catch (ex) {
                  return reject(ex);
                }
              } else {
                return reject(error);
              }
            });
          });
        }
        // ...
    }

### $.promise

jQuery 1.8 之前的版本,jQuery的 then 方法只是一种可以同时调用 done 、fail 和 progress 这三种回调的速写方法,而 Promises/A 规范的 then 在行为上更像是 jQuery 的 pipe。 jQuery 1.8 修正了这个问题,使 then 成为 pipe 的同义词。不过,由于向后兼容的问题,jQuery 的 Promise 再如何对 Promises/A 示好也不太会招人待见。

此外,在 Promises/A 规范中,由 then 方法生成的 Promise 对象是已执行还是已拒绝,取决于由 then 方法调用的那个回调是返回值还是抛出错误。在 JQuery 的 Promise 对象的回调中抛出错误是个糟糕的主意,因为错误不会被捕获。

### 小结

最后一个例子揭示了,实现 Promise 的关键是实现好 doResolve 方法,在完事以后触发回调。而为了保证异步 `setTimeout(fun, 0);` 是关键一步。

Promise 一直用得蛮顺手的,其很好的优化了 NodeJS 异步处理时的代码结构。但是对于其工作原理却有些懵懂和好奇。于是花了些经理查阅并翻译了Promise 的规范,以充分的理解 Promise 的细节。


### 参考阅读

1. [Promises/A](http://wiki.commonjs.org/wiki/Promises/A)
2. [Promises/B](http://wiki.commonjs.org/wiki/Promises/B)
3. [Promises/D](http://wiki.commonjs.org/wiki/Promises/D)
4. [Promisejs](https://www.promisejs.org/)
5. [Promises/A+](https://promisesaplus.com/)
6. [As soon as possible](https://github.com/kriskowal/asap)
7. [A minimalist implementation of a javascript promise](https://gist.github.com/unscriptable/814052)
8. [Lightweight implementation of promises](http://stackoverflow.com/questio ... ntation-of-promises)
9. [How is a promise/defer library implemented?](http://stackoverflow.com/questio ... library-implemented)
10. [Basic Javascript promise implementation attempt](http://stackoverflow.com/questio ... t/23785244#23785244)
11. [You're Missing the Point of Promises](https://blog.domenic.me/youre-missing-the-point-of-promises/)
12. [Boom! Promises/A+ Was Born](http://www.slideshare.net/domenicdenicola/boom-promisesa-was-born)
13. [Futures and promises](http://en.wikipedia.org/wiki/Futures_and_promises)
14. [JavaScript Promises - There and back again](http://www.html5rocks.com/zh/tutorials/es6/promises/)


> ![图片](?imageMogr2/auto-orient/format/jpeg/crop/!398x398a0a0/thumbnail/80)
> #### [Vangie Du](https://coding.net/u/duwan)
> 将来的你,一定会感谢现在拼命努力的自己!

ChinaGDG.com
回复

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表