在以前的异步JavaScript与Promise一文中,我介绍了Promise以及它在异步JavaScript中的使用意义。通常来讲,咱们是经过各类JavaScript库来应用Promise的。随着使用Promise的机会愈来愈多,你也可能像我这样会关心Promise究竟是如何工做的。显然,了解Promise的实现细节,能够帮助咱们更好地应用它。尤为是碰到一些Promise的问题时,也许能够更快速、更准确地定位缘由,并解决它。segmentfault
很是庆幸,在[Promises/A wiki][]中位于库列表第一位的[Q][],提供了它做为一个Promise库的[基本设计原理解析][]。本文将主要根据Q的这篇文章,探讨Promise的实现细节。数组
尽管Promise已经有本身的规范,但目前的各种Promise库,在Promise的实现细节上是有差别的,部分API甚至在乎义上彻底不一样。但Promise的核心内容,是相通的,它就是then
方法。在相关术语中,promise
指的就是一个有then
方法,且该方法能触发特定行为的对象或函数。promise
有关Promise核心说明的细节,推荐阅读[Promises/A+][]。这篇文章是写给Promise库的开发者的,你能够找到各类对Promise特性的说明。[Promises/A+][]但愿开发者听从这些特性,以实现能够共同使用的Promise(也就是说,不一样的Promise库也可共用)。app
Promise能够有不一样的实现方式,所以Promise核心说明并不会讨论任何具体的实现代码。异步
先阅读Promise核心说明的意思是:看,这就是须要写出来的结果,请参照这个结果想想怎么用代码写出来吧。ide
回想一下Promise解决的是什么问题?回调。例如,函数doMission1()
表明第一件事情,如今,咱们想要在这件事情完成后,再作下一件事情doMission2()
,应该怎么作呢?函数
先看看咱们常见的回调模式。doMission1()
说:“你要这么作的话,就把doMission2()
交给我,我在结束后帮你调用。”因此会是:工具
doMission1(doMission2);
Promise模式又是如何呢?你对doMission1()
说:“不行,控制权要在我这里。你应该改变一下,你先返回一个特别的东西给我,而后我来用这个东西安排下一件事。”这个特别的东西就是Promise,这会变成这样:post
doMission1().then(doMission2);
能够看出,Promise将回调模式的主从关系调换了一个位置(翻身作主人!),多个事件的流程关系,就能够这样集中到主干道上(而不是分散在各个事件函数以内)。设计
好了,如何作这样一个转换呢?从最简单的状况来吧,假定doMission1()
的代码是:
function doMission1(callback){ var value = 1; callback(value); }
那么,它能够改变一下,变成这样:
function doMission1(){ return { then: function(callback){ var value = 1; callback(value); } }; }
这就完成了转换。虽然并非实际有用的转换,但到这里,其实已经触及了Promise最为重要的实现要点,即Promise将返回值转换为带then
方法的对象。
design/q0.js
是Q初步成型的第一步。它建立了一个名为defer
的工具函数,用于建立Promise:
var defer = function () { var pending = [], value; return { resolve: function (_value) { value = _value; for (var i = 0, ii = pending.length; i < ii; i++) { var callback = pending[i]; callback(value); } pending = undefined; }, then: function (callback) { if (pending) { pending.push(callback); } else { callback(value); } } } };
这段源码能够看出,运行defer()
将获得一个对象,该对象包含resolve
和then
两个方法。请回想一下jQuery的Deferred(一样有resolve
和then
),这两个方法将会是近似的效果。then
会参考pending
的状态,若是是等待状态则将回调保存(push
),不然当即调用回调。resolve
则将确定这个Promise,更新值的同时运行完全部保存的回调。defer
的使用示例以下:
var oneOneSecondLater = function () { var result = defer(); setTimeout(function () { result.resolve(1); }, 1000); return result; }; oneOneSecondLater().then(callback);
这里oneOneSecondLater()
包含异步内容(setTimeout
),但这里让它当即返回了一个defer()
生成的对象,而后将对象的resolve
方法放在异步结束的位置调用(并附带上值,或者说结果)。
到此,以上代码存在一个问题:resolve
能够被执行屡次。所以,resolve
中应该加入对状态的判断,保证resolve
只有一次有效。这就是Q下一步的design/q1.js
(仅差别部分):
resolve: function (_value) { if (pending) { value = _value; for (var i = 0, ii = pending.length; i < ii; i++) { var callback = pending[i]; callback(value); } pending = undefined; } else { throw new Error("A promise can only be resolved once."); } }
对第二次及更多的调用,能够这样抛出一个错误,也能够直接忽略掉。
在前面的实现中,defer
生成的对象同时拥有then
方法和resolve
方法。按照定义,promise关心的是then
方法,至于触发promise改变状态的resolve
,是另外一回事。因此,Q接下来将拥有then
方法的promise,和拥有resolve
的defer分离开来,各自独立使用。这样就好像划清了各自的职责,各自只留必定的权限,这会使代码逻辑更明晰,易于调整。请看design/q3.js
:(q2
在此跳过)
var isPromise = function (value) { return value && typeof value.then === "function"; }; var defer = function () { var pending = [], value; return { resolve: function (_value) { if (pending) { value = _value; for (var i = 0, ii = pending.length; i < ii; i++) { var callback = pending[i]; callback(value); } pending = undefined; } }, promise: { then: function (callback) { if (pending) { pending.push(callback); } else { callback(value); } } } }; };
若是你仔细对比一下q1
,你会发现区别很小。一方面,再也不抛出错误(改成直接忽略第二次及更多的resolve
),另外一方面,将then
方法移动到一个名为promise
的对象内。到这里,运行defer()
获得的对象(就称为defer吧),将拥有resolve
方法,和一个promise
属性指向另外一个对象。这另外一个对象就是仅有then
方法的promise。这就完成了分离。
前面还有一个isPromise()
函数,它经过是否有then
方法来判断对象是不是promise(duck-typing的判断方法)。为了正确使用和处理分离开的promise,会像这样须要将promise和其余值区分开来。
接下来会是至关重要的一步。到前面到q3
为止,所实现的promise都是不能级联的。但你所熟知的promise应该支持这样的语法:
promise.then(step1).then(step2);
以上过程能够理解为,promise将能够创造新的promise,且取自旧的promise的值(前面代码中的value
)。要实现then
的级联,须要作到一些事情:
then
方法必须返回promise。
这个返回的promise必须用传递给then
方法的回调运行后的返回结果,来设置本身的值。
传递给then
方法的回调,必须返回一个promise或值。
design/q4.js
中,为了实现这一点,新增了一个工具函数ref
:
var ref = function (value) { if (value && typeof value.then === "function") return value; return { then: function (callback) { return ref(callback(value)); } }; };
这是在着手处理与promise关联的value。这个工具函数将对任一个value值作一次包装,若是是一个promise,则什么也不作,若是不是promise,则将它包装成一个promise。注意这里有一个递归,它确保包装成的promise可使用then
方法级联。为了帮助理解它,下面是一个使用的例子:
ref("step1").then(function(value){ console.log(value); // "step1" return 15; }).then(function(value){ console.log(value); // 15 });
你能够看到value是怎样传递的,promise级联须要作到的也是如此。
design/q4.js
经过结合使用这个ref
函数,将原来的defer转变为可级联的形式:
var defer = function () { var pending = [], value; return { resolve: function (_value) { if (pending) { value = ref(_value); // values wrapped in a promise for (var i = 0, ii = pending.length; i < ii; i++) { var callback = pending[i]; value.then(callback); // then called instead } pending = undefined; } }, promise: { then: function (_callback) { var result = defer(); // callback is wrapped so that its return // value is captured and used to resolve the promise // that "then" returns var callback = function (value) { result.resolve(_callback(value)); }; if (pending) { pending.push(callback); } else { value.then(callback); } return result.promise; } } }; };
原来callback(value)
的形式,都修改成value.then(callback)
。这个修改后效果其实和原来相同,只是由于value
变成了promise包装的类型,会须要这样调用。
then
方法有了较多变更,会先新生成一个defer,并在结尾处返回这个defer的promise。请注意,callback
再也不是直接取用传递给then
的那个,而是在此基础之上增长一层,并把新生成的defer的resolve
方法放置在此。此处能够理解为,then
方法将返回一个新生成的promise,所以须要把promise的resolve也预留好,在旧的promise的resolve运行后,新的promise的resolve也会随之运行。这样才能像管道同样,让事件按照then
链接的内容,一层一层传递下去。
promise的then
方法应该能够包含两个参数,分别是确定和否认状态的处理函数(onFulfilled
与onRejected
)。前面咱们实现的promise还只能转变为确定状态,因此,接下来应该加入否认状态部分。
请注意,promise的then
方法的两个参数,都是可选参数。design/q6.js
(q5
也跳过)加入了工具函数reject
来帮助实现promise的否认状态:
var reject = function (reason) { return { then: function (callback, errback) { return ref(errback(reason)); } }; };
它和ref
的主要区别是,它返回的对象的then
方法,只会取第二个参数的errback
来运行。design/q6.js
的其他部分是:
var defer = function () { var pending = [], value; return { resolve: function (_value) { if (pending) { value = ref(_value); for (var i = 0, ii = pending.length; i < ii; i++) { value.then.apply(value, pending[i]); } pending = undefined; } }, promise: { then: function (_callback, _errback) { var result = defer(); // provide default callbacks and errbacks _callback = _callback || function (value) { // by default, forward fulfillment return value; }; _errback = _errback || function (reason) { // by default, forward rejection return reject(reason); }; var callback = function (value) { result.resolve(_callback(value)); }; var errback = function (reason) { result.resolve(_errback(reason)); }; if (pending) { pending.push([callback, errback]); } else { value.then(callback, errback); } return result.promise; } } }; };
这里的主要改动是,将数组pending
只保存单个回调的形式,改成同时保存确定和否认的两种回调的形式。并且,在then
中定义了默认的确定和否认回调,使得then
方法知足了promise的2个可选参数的要求。
你也许注意到defer中仍是只有一个resolve
方法,而没有相似jQuery的reject
。那么,错误处理要如何触发呢?请看这个例子:
var defer1 = defer(), promise1 = defer1.promise; promise1.then(function(value){ console.log("1: value = ", value); return reject("error happens"); }).then(function(value){ console.log("2: value = ", value); }).then(null, function(reason){ console.log("3: reason = ", reason); }); defer1.resolve(10); // Result: // 1: value = 10 // 3: reason = error happens
能够看出,每个传递给then
方法的返回值是很重要的,它将决定下一个then
方法的调用结果。而若是像上面这样返回工具函数reject
生成的对象,就会触发错误处理。
终于到了最后的design/q7.js
。直到前面的q6
,还存在一个问题,就是then
方法运行的时候,多是同步的,也多是异步的,这取决于传递给then
的函数(例如直接返回一个值,就是同步,返回一个其余的promise,就能够是异步)。这种不肯定性可能带来潜在的问题。所以,Q的后面这一步,是确保将全部then
转变为异步。
design/q7.js
定义了另外一个工具函数enqueue
:
var enqueue = function (callback) { //process.nextTick(callback); // NodeJS setTimeout(callback, 1); // Naïve browser solution };
显然,这个工具函数会将任意函数推迟到下一个事件队列运行。
design/q7.js
其余的修改点是(只显示修改部分):
var ref = function (value) { // ... return { then: function (callback) { var result = defer(); // XXX enqueue(function () { result.resolve(callback(value)); }); return result.promise; } }; }; var reject = function (reason) { return { then: function (callback, errback) { var result = defer(); // XXX enqueue(function () { result.resolve(errback(reason)); }); return result.promise; } }; }; var defer = function () { var pending = [], value; return { resolve: function (_value) { // ... enqueue(function () { value.then.apply(value, pending[i]); }); // ... }, promise: { then: function (_callback, _errback) { // ... enqueue(function () { value.then(callback, errback); }); // ... } } }; };
即把原来的value.then
的部分,都转变为异步。
到此,Q提供的Promise设计原理q0
~q7
,所有结束。
即使本文已是这么长的篇幅,但所讲述的也只到基础的Promise。大部分Promise库会有更多的API来应对更多和Promise有关的需求,例如all()
、spread()
,不过,读到这里,你已经了解了实现Promise的核心理念,这必定对你从此应用Promise有所帮助。
在我看来,Promise是精巧的设计,我花了至关一些时间才差很少理解它。Q做为一个典型Promise库,在思路上走得很明确。能够感觉到,再复杂的库也是先从基本的要点开始的,若是咱们本身要作相似的事,也应该保持这样的心态一点一点进步。
(从新编辑自个人博客,原文地址:http://acgtofe.com/posts/2015...)