深入理解JavaScript Promise:从Q库源码看核心实现原理
本文深入剖析JavaScript Promise的核心机制,以Q库的源码演进为例,详细讲解从基础回调到Promise对象的转换过程。内容涵盖defer与promise的分离、级联调用的实现原理、错误处理机制以及异步执行的确保策略,帮助开发者理解Promise背后的设计逻辑与实现细节。
Promise的核心机制与基础概念
尽管Promise已有规范,但各类库在实现细节上存在差异,部分API甚至含义不同。然而,Promise的核心在于then方法。术语中,promise指代具有then方法且能触发特定行为的对象或函数。
由于Promise存在多种实现方式,核心说明不讨论具体代码。
阅读核心说明的目的是明确最终结果,从而思考如何用代码实现。
起步:用这一种方式理解Promise
回想Promise解决的是回调问题。例如,doMission1()代表第一件事,完成后需执行doMission2(),该如何操作?
常见回调模式中,doMission1()要求将doMission2()作为参数传入,它在结束后调用该函数。
代码如下:
doMission1(doMission2);
Promise模式下,控制权在调用者手中。doMission1()应返回一个特殊对象(即Promise),由调用者通过该对象安排后续事件。
代码如下:
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最重要的实现要点:将返回值转换为带then方法的对象。
Q库源码演进:从defer到级联
进阶:Q的设计路程
从defer开始
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类似,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的值。要实现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。若为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来帮助实现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应对更多需求,如all()、spread()。但读到这里,你已了解实现Promise的核心理念,这对你今后应用Promise必有帮助。
在我看来,Promise是精巧的设计,我花了相当时间才差不多理解它。Q作为典型Promise库,思路明确。可感受到,再复杂的库也是从基本要点开始,若自己做类似事,也应保持此心态一点一点进步。
以上就是本文的全部内容,希望对大家的学习有所帮助。
JavaScript异步发送HTTP请求时怎么优雅地处理错误
2026-11-05 17:10:44
5款主流JS在线运行工具对比:CodePen、JSFiddle与StackBlitz怎么选
2026-11-05 17:04:04
JavaScript入门书籍避坑:这些书不适合初学者
2026-11-05 16:31:15
JS使用Math.round实现保留两位小数的原理与代码
2026-11-05 15:35:55
JavaScript 原生 Date 对象怎么实现日期加减
2026-11-05 14:30:00
