深入理解JavaScript Promise:从规范演进到核心实现

作者:半夏

时间:2026-10-29

浏览:226

本文深入解析JavaScript中Promise的工作原理,从解决回调地狱的痛点出发,详细梳理Promises/A、B、D及A+规范的核心差异与演进。通过对比各语言实现,重点剖析Promise的状态机机制、then方法链式调用逻辑,并基于迷你版源码拆解resolve、doResolve等关键函数的实现细节,帮助开发者彻底掌握异步编程的最佳实践。

文章目录

Promise 的起源与定义

JavaScript 早期依赖回调函数(callback)处理异步逻辑。从同步思维转向异步回调需要适应期,而多层回调嵌套形成的“回调地狱”(Pyramid of Doom)更是严重降低代码可读性与维护性。为此,CommonJS 组织提出了 Promises/A 规范,旨在消除嵌套回调带来的复杂性。本文将先介绍相关规范,随后通过解读一个精简的 Promise 实现来深化理解。

Promise 对象代表一个当前不可用、但未来某个时刻可解析的值。它允许开发者以看似同步的方式编写异步代码。例如,发起远程服务器请求时,需创建一个 Promise 对象来代理未来由 Web 服务返回的数据。在数据返回前,Promise 充当占位符;一旦请求完成并获取数据,绑定的回调函数便会执行。

Promise 的概念并非 JavaScript 独有,它在多种编程语言中已有不同形式的实现。

消除回调地狱

回调地狱(Pyramid of Doom)是 JavaScript 中最常见的反模式,即在回调函数内部再次嵌套其他回调。


// 回调金字塔
asyncOperation(function(data){
 // 处理 `data`
 anotherAsync(function(data2){
   // 处理 `data2`
   yetAnotherAsync(function(){
     // 完成
   });
 });
});

引入 Promises 后,代码结构发生显著变化。


promiseSomething()
.then(function(data){
  // 处理 `data`
  return anotherAsync();
})
.then(function(data2){
  // 处理 `data2`
  return yetAnotherAsync();
})
.then(function(){
  // 完成
});

Promises 将嵌套的回调转换为一系列 .then 方法的链式调用,消除了层层缩进带来的代码风格问题。Promises 并非解决具体业务逻辑的算法,而是一种更优的代码组织模式。接受这种新模式,有助于以全新视角理解异步调用。

各语言平台的 Promise 实现

不同编程语言平台均提供了各自的 Promise 实现方案。

  • Java 的 java.util.concurrent.Future
  • Python 的 Twisted deferreds 和 PEP-3148 futures
  • F# 的 Async
  • .Net 的 Task
  • C++ 11 的 std::future
  • Dart 的 Future
  • JavaScript 的 Promises/A/B/D/A+ 规范

JavaScript Promise 规范演进

接下来,我们将深入了解 JavaScript 环境下各规范的具体细节。

Promises/A 规范详解

Promises/A 规范规定,promise 表示一个最终值,该值由操作完成时返回。

  • promise 拥有三种状态:**未完成** (unfulfilled),**完成** (fulfilled) 和**失败** (failed)。
  • promise 的状态只能从**未完成**转换为**完成**,或从**未完成**转换为**失败**。
  • promise 的状态转换仅发生一次。

promise 包含一个 then 方法,该方法可接受三个函数作为参数。前两个函数分别对应 promise 处于 fulfilled 和 rejected 状态时的回调;第三个函数用于处理进度信息(支持进度回调为可选)。


promiseSomething().then(function(fulfilled){
    //当promise状态变成fulfilled时,调用此函数
  },function(rejected){
    //当promise状态变成rejected时,调用此函数
  },function(progress){
    //当返回进度信息时,调用此函数
  });

若 promise 支持以下两个附加方法,则称为可交互的 promise。

  • get(propertyName)

获取当前 promise 最终值上的一个属性,返回值是一个新的 promise。

  • call(functionName, arg1, arg2, ...)

调用当前 promise 最终值上的一个方法,返回值也是一个新的 promise。

Promises/B 规范 API

在 Promises/A 基础上,Promises/B 定义了一组 promise 模块需实现的 API。

when(value, callback, errback_opt) 如果 value 不是 promise,下一事件循环中 callback 将被调用,value 作为参数传入。若 value 是 promise,当其状态变为完成或已完成后,下一事件循环中 callback 将被调用,resolve 的值传入 callback;若状态变为失败或已失败,下一事件循环中 errback 将被调用,reason 作为失败理由传入。

asap(value, callback, errback_opt) 与 when 最大区别在于,若 value 不是 promise,将立即执行,无需等待下一事件循环。

enqueue(task Function) 尽可能快地在下一事件循环调用 task 方法。

get(object, name) 返回一个获取对象属性的 promise。

post(object, name, args) 返回一个调用对象方法的 promise。

put(object, name, value) 返回一个修改对象属性的 promise。

del(object, name) 返回一个删除对象属性的 promise。

makePromise(descriptor Object, fallback Function) 返回一个 promise 对象,该对象必须是可调用的函数,或是可被实例化的构造函数。

  • 第一个参数接受一个描述对象,其结构如下。

{ "when": function(errback){...}, "get": function(name){...}, "put": function(name, value){...}, "post": function(name, args){...}, "del": function(name){...}, } 

上述每个注册的 handle 都返回一个 resolved value 或 promise。

  • 第二个参数接受一个 fallback(message,...args) 函数,当未找到对应 promise 对象时触发该函数,返回 resolved value 或 promise。

defer() 返回一个包含 resolve(value) 方法和 promise 属性的对象。 当 resolve(value) 被首次调用时,promise 属性状态变为完成,所有之前或之后观察该 promise 的状态均转为完成。若 value 非 promise,会被包装为 promise ref。resolve 方法忽略后续所有调用。

reject(reason String) 返回一个标记为失败的 promise。 对失败 promise 调用 when(message) 时,采用以下两种方法之一: 1. 若存在 errback,以 reason 为参数调用 errback,when 方法返回 errback 的返回值。 2. 若不存在 errback,when 方法返回一个新的 reject 状态 promise,以同一 reason 为参数。

ref(value) 若 value 是 promise,返回 value 本身。否则,返回一个 resolved 的 promise,携带如下 handle。 1. when(errback),忽略 errback,返回 resolved 值 2. get(name),返回 resolved 值的对应属性。 3. put(name, value),设置 resolved 值的对应属性。 4. del(name),删除 resolved 值的对应属性。 5. post(name, args),调用 resolved 值的对应方法。 6. 其他所有调用均返回 reject,并携带 "Promise does not handle NAME" 的理由。

isPromise(value) Boolean 判断一个对象是否为 promise。

method(name String) 获取返回 name 对应方法的 promise。返回值是 "get", "put", "del" 和 "post" 对应的方法,但会在下一事件循环返回。

Promises/D 与 A+ 规范

Promises/D 规范 为提高不同 promise 实现间的互操作性,Promises/D 规范对 promise 对象和 Promises/B 规范做了进一步约定,以实现鸭子类型(Duck-type Promise)。

简而言之,Promises/D 规范主要做了两件事。

1、如何判断一个对象是 Promise 类型。 2、对 Promises/B 规范进行细节补充。 甄别一个 Promise 对象 Promise 对象必须实现 promiseSend 方法。 1. 在 promise 库上下文中,若对象包含 promiseSend 方法,即可甄别为 promise 对象 2. promiseSend 方法必须接受一个操作名称作为第一个参数 3. 操作名称是一个可扩展集合,部分保留名称如下: 1. when,此时第三个参数必须是 rejection 回调。 1. rejection 回调必须接受一个 rejection 原因(可为任何值)作为第一个参数 2. get,此时第三个参数为属性名(字符串类型) 3. put,此时第三个参数为属性名(字符串类型),第四个参数为新属性值。 4. del,此时第三个参数为属性名 5. post,此时第三个参数为方法的属性名,后续变参为方法的调用参数 6. isDef 4. promiseSend 方法的第二个参数为 resolver 方法 5. promiseSend 方法可能接受变参 6. promiseSend 方法必须返回 undefined

对 Promises/B 规范的补充 Promises/D 规范对 Promises/B 中定义的 ref、reject、def、defer 方法做了进一步细致约束,此处略去这些细节。

Promises/A+ 规范 前述 Promises/A/B/D 规范均由 CommonJS 组织提出,而 Promises/A+ 由自称 Promises/A+ 组织发布。该规范以 Promises/A 为基础进行补充和修订,旨在提高 promise 实现间的互操作性。

Promises/A+ 对 .then 方法做了细致补充,定义了详细的 Promise Resolution Procedure 流程,并将 .then 方法作为 promise 的对象甄别方法。

此外,Promises/A+ 还提供了兼容性测试工具,以确定各实现的兼容性。

迷你 Promise 源码实现

实现一个迷你版本的 Promise 前述规范介绍完毕后,我们来看看如何实现一个简单而短小的 Promise。

1、状态机


var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;

function Promise() {
 // store state which can be PENDING, FULFILLED or REJECTED
 var state = PENDING;

 // store value or error once FULFILLED or REJECTED
 var value = null;

 // store sucess & failure handlers attached by calling .then or .done
 var handlers = [];
}

2、状态变迁 仅支持两种状态变迁:fulfill 和 reject。


// ...

function Promise() {
  // ...

 function fulfill(result) {
  state = FULFILLED;
  value = result;
 }

 function reject(error) {
  state = REJECTED;
  value = error;
 }

}

fulfill 和 reject 方法较为底层,通常更高级的 resolve 方法开放给外部。


// ...

function Promise() {

 // ...

 function resolve(result) {
  try {
   var then = getThen(result);
   if (then) {
    doResolve(then.bind(result), resolve, reject)
    return
   }
   fulfill(result);
  } catch (e) {
   reject(e);
  }
 }
}

resolve 方法可接受普通值或另一个 promise 作为参数。若接受 promise,则等待其完成。由于 promise 不允许被另一个 promise fulfill,因此需开放 resolve 方法。resolve 方法依赖一些帮助方法定义如下。


/**
 * Check if a value is a Promise and, if it is,
 * return the `then` method of that promise.
 *
 * @param {Promise|Any} value
 * @return {Function|Null}
 */
function getThen(value) {
 var t = typeof value;
 if (value && (t === 'object' || t === 'function')) {
  var then = value.then;
  if (typeof then === 'function') {
   return then;
  }
 }
 return null;
}

/**
 * Take a potentially misbehaving resolver function and make sure
 * onFulfilled and onRejected are only called once.
 *
 * Makes no guarantees about asynchrony.
 *
 * @param {Function} fn A resolver function that may not be trusted
 * @param {Function} onFulfilled
 * @param {Function} onRejected
 */
function doResolve(fn, onFulfilled, onRejected) {
 var done = false;
 try {
  fn(function (value) {
   if (done) return
   done = true
   onFulfilled(value)
  }, function (reason) {
   if (done) return
   done = true
   onRejected(reason)
  })
 } catch (ex) {
  if (done) return
  done = true
  onRejected(ex)
 }
}

此处 resolve 和 doResolve 之间的递归处理巧妙,用于处理 promise 的层层嵌套(即 promise 的 value 是一个 promise)。

构造器


// ...

function Promise(fn) {
  // ...
  doResolve(fn, resolve, reject);
}

.done 方法


// ...
function Promise(fn) {
 // ...

 function handle(handler) {
  if (state === PENDING) {
   handlers.push(handler);
  } else {
   if (state === FULFILLED &&
    typeof handler.onFulfilled === 'function') {
    handler.onFulfilled(value);
   }
   if (state === REJECTED &&
    typeof handler.onRejected === 'function') {
    handler.onRejected(value);
   }
  }
 }

 this.done = function (onFulfilled, onRejected) {
  // ensure we are always asynchronous
  setTimeout(function () {
   handle({
    onFulfilled: onFulfilled,
    onRejected: onRejected
   });
  }, 0);
 }
 // ...
}

.then 方法


// ...
function Promise(fn) {
  // ...
  this.then = function (onFulfilled, onRejected) {
   var self = this;
   return new Promise(function (resolve, reject) {
    return self.done(function (result) {
     if (typeof onFulfilled === 'function') {
      try {
       return resolve(onFulfilled(result));
      } catch (ex) {
       return reject(ex);
      }
     } else {
      return resolve(result);
     }
    }, function (error) {
     if (typeof onRejected === 'function') {
      try {
       return resolve(onRejected(error));
      } catch (ex) {
       return reject(ex);
      }
     } else {
      return reject(error);
     }
    });
   });
  }
  // ...
}

$.promise jQuery 1.8 之前的版本中,jQuery 的 then 方法仅是同时调用 done、fail 和 progress 三种回调的速写方法,而 Promises/A 规范的 then 在行为上更像 jQuery 的 pipe。jQuery 1.8 修正了此问题,使 then 成为 pipe 的同义词。不过,出于向后兼容考虑,jQuery 的 Promise 即便对 Promises/A 示好,也不易被广泛接受。

此外,在 Promises/A 规范中,由 then 方法生成的 Promise 对象是已执行还是已拒绝,取决于 by then 方法调用的那个回调是返回值还是抛出错误。在 JQuery 的 Promise 对象的回调中抛出错误是个糟糕的主意,因为错误不会被捕获。

总结与回顾

小结 最后一个例子揭示,实现 Promise 的关键是实现好 doResolve 方法,在完成后触发回调。而为了保证异步性,setTimeout(fun, 0) 是关键一步。

Promise 使用顺畅,极大优化了 NodeJS 异步处理时的代码结构。但对其工作原理仍感懵懂好奇,因此花费精力查阅并翻译 Promise 规范,以充分理解 Promise 的细节。

以上就是关于 JavaScript 中 Promise 使用方法介绍,希望对大家的学习有所帮助。

实用工具推荐

电子包浆

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

使用 ›
图片压缩

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

使用 ›
相关文章