深入理解JavaScript Promise:从Q库源码看核心实现原理

作者:代码拾荒

时间:2026-10-30

浏览:376

本文深入剖析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库,思路明确。可感受到,再复杂的库也是从基本要点开始,若自己做类似事,也应保持此心态一点一点进步。

以上就是本文的全部内容,希望对大家的学习有所帮助。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章