理解 Promise 的工做原理

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

什么是 Promise

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

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

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

Javascript 中最多见的反模式作法是回调内部再嵌套回调。github

// 回调金字塔
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 实现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 的回调函数。第三个函数用于处理进度信息(对进度回调的支持是可选的)。async

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+ 组织发布的,该规范是以 Promises/A 做为基础进行补充和修订,旨在提升 promise 实现之间的可互操做性。

Promises/A+ 对.then方法进行细致的补充,定义了细致的Promise Resolution Procedure流程,而且将.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 的细节。

相关文章
相关标签/搜索