本文深入解析JavaScript Promise的核心机制,重点剖析Q库从基础概念到完整实现的演进过程。通过对比传统回调模式,阐述Promise如何通过then方法统一异步流程。文章详细拆解Q库的q0至q7版本,涵盖defer与promise分离、级联实现、错误处理及异步调度等关键技术点,帮助开发者理解Promise的设计哲学与底层逻辑。
Promise核心机制解析
Promise的核心概念
尽管各类Promise库在实现细节上存在差异,部分API甚至含义不同,但Promise的核心是相通的,即then方法。在术语定义中,Promise指的是一个包含then方法的对象或函数,该方法能够触发特定的行为。
由于Promise存在多种实现方式,核心说明部分不涉及具体代码实现。
阅读核心说明的目的是明确最终需要实现的效果,从而反推代码编写思路。
回顾Promise旨在解决的问题:回调。例如,函数doMission1()代表第一项任务,若要在其完成后执行doMission2(),应如何处理?
常见的回调模式中,doMission1()要求调用者提供doMission2()作为参数,并在任务结束后自行调用。其调用形式为。
doMission1(doMission2);
在Promise模式下,调用者掌握控制权,要求doMission1()返回一个特殊对象(即Promise),由调用者利用该对象安排后续任务。形式变为。
doMission1().then(doMission2);
这种模式翻转了回调的主从关系,使多个事件的流程集中到主干道上,而非分散在各个事件函数内部。
从最简单情况开始转换,假设doMission1()的代码如下:。
function doMission1(callback){
var value = 1;
callback(value);
}
将其改造为:。
function doMission1(){
return {
then: function(callback){
var value = 1;
callback(value);
}
};
}
Q库基础实现:从defer开始
这完成了初步转换。虽然并非实际可用的代码,但触及了Promise最重要的实现要点:将返回值转换为带有then方法的对象。
Q库的初步实现始于design/q0.js,它创建了一个名为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类似:then方法根据pending状态保存或立即调用回调;resolve方法确认Promise并更新值,同时执行所有保存的回调。使用示例如下:。
var oneOneSecondLater = function () {
var result = defer();
setTimeout(function () {
result.resolve(1);
}, 1000);
return result;
};
oneOneSecondLater().then(callback);
oneOneSecondLater()包含异步内容(setTimeout),但立即返回defer()生成的对象,并将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与promise
在前面的实现中,defer生成的对象同时拥有then和resolve方法。根据定义,Promise关注then方法,而改变状态的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方法和指向另一个仅有then方法的promise对象的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可使用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,因此需预留其resolve,以便在旧Promise的resolve运行后,新Promise的resolve随之运行。如此如管道般,使事件按then连接内容层层传递。
加入错误处理
Promise的then方法应包含两个参数,分别处理肯定和否定状态(onFulfilled与onRejected)。前文实现的Promise仅能转为肯定状态,接下来需加入否定状态部分。
注意,Promise的then方法两个参数均为可选。design/q6.js(跳过q5)加入工具函数reject以辅助实现否定状态:。
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两个可选参数的要求。
或许注意到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以应对相关需求,例如all()、spread()等。但理解实现Promise的核心理念,必将对今后应用Promise有所帮助。
在我看来,Promise是精巧的设计,我花费相当时间才大致理解。Q作为典型Promise库,思路明确。可感受到,再复杂的库亦从基本要点起步,若自行实现类似功能,也应保持此心态逐步进步。