深入解析JavaScript Promise核心机制与Q库演进

作者:闭包旅人时间:2026-10-29 12:43:15浏览:328

本文深入解析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库,思路明确。可感受到,再复杂的库亦从基本要点起步,若自行实现类似功能,也应保持此心态逐步进步。

作者最新文章