news 2026/9/21 16:00:17

Q 1.x 版本演进全记录:从 CHANGES.md 解读 JavaScript Promise 库 Q 的 API 变迁与实现细节

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Q 1.x 版本演进全记录:从 CHANGES.md 解读 JavaScript Promise 库 Q 的 API 变迁与实现细节
  • 异步编程

【免费下载链接】q

A promise library for JavaScript

项目地址:https://gitcode.com/gh_mirrors/q/q
点击查看免费下载

Q 是 JavaScript 早期的 Promise 库,其 CHANGES.md 以版本为索引,完整记录了从 0.0.1 到 1.5.1 的 API 演进、破坏性变更与底层实现调整。本文以该变更日志为骨架,结合 q.js 源码与 spec/q-spec.js 测试,梳理 Q 1.x 系列的核心能力:Q.anyQ.Promise、长堆栈追踪、未处理拒绝追踪、noConflicttap等,并还原 0.9.x 时代的关键 API 清理与命名演变,帮助你理解"现代 Promise 之前"的异步编程生态。

版本总览与演进主线

CHANGES.md 覆盖了 Q 从 0.0.1(初始版本)到 1.5.1(当前仓库版本,见 package.json 中"version": "1.5.1")的全部历史。整体演进可以划分为三条主线:

  • API 从实验走向稳定:0.2.x 的post/invoke之争、0.8.x 的call/apply/bind体系、0.9.0 的大规模 API 清理,最终收敛为 1.0 之后的稳定形态。
  • 能力不断向 ES6 Promise 靠拢Q.Promise构造器(1.0.1)、Q.any(1.2.0)、raceallSettled等组合方法逐步补齐。
  • 错误处理与调试体验持续增强:长堆栈追踪(0.8.5 起)、未处理拒绝追踪(0.9.4/1.3.0)、done/nodeify等终止链式调用并暴露错误的手段。

1.0.0 的发布说明很能说明项目定位:"这几乎就是 0.9 的重发布,0.9 已进入温和的维护模式,理应获得官方 1.0 的身份。野心勃勃的 2.0 即将到来,但 0.9/1.0 已被广泛分发,需要长期支持。"(见 CHANGES.md 1.0.0 小节)。因此 1.x 系列本质上是 0.9 的稳定延续,CHANGES.md 中 1.x 的条目也主要围绕新增 API 与缺陷修复。

Q 1.x 系列:稳定期的功能补全

1.5.x:Q.any错误信息与 finally 校验

1.5.1有两处改动:

  1. Q.any现在会在错误信息中注明"Q.any 参与其中",并且只包含最后一个被拒绝的 promise 的错误信息(由 Ivan Etchart 贡献)。查看 q.js 中anyonRejected处理:当所有 promise 都被拒绝时,会构造rejection.message = "Q can't get fulfillment value from any promise, all promises were rejected. Last error message: ...",这正是 1.5.1 所描述的行为。
  2. 在测试中避免使用domain.dispose,为 Node.js 9 做准备(由 Anna Henningsen 贡献),对应测试代码在 spec/q-spec.js 中。

1.5.0包含三项能力:

  • Q.any的错误信息取自最后一个被拒绝的 promise(延续 1.5.1 的基础工作)。
  • 如果传给finally(即fin)的回调不是函数,现在会抛出异常(由 @grahamrhay 贡献)。在源码中Promise.prototype.fin定义于 q.js 附近,该校验保证了fin/finally的调用安全性。
  • 长堆栈追踪的重大改进:现在可以在跨throw/rethrow 时构造长堆栈。此前长堆栈只能追踪单次异步跳转(0.9.5 才支持完整历史追踪),1.5.0 之后即使错误被重新抛出,Q 也能把完整的异步历史拼接到error.stack上。

1.4.x:浏览器<script>场景的补强

1.4.1修复了一个阻止 Q 作为 Firefox 附加组件<script>使用的问题。Q 现在可以在任何提供windowself全局变量的环境中运行,并且优先使用window——因为附加组件的self是不可变的,且与window不同。对应实现位于 q.js:模块封装器先判断 CommonJS/RequireJS/SES,再回退到window/self分支。

1.4.0增加了noConflict支持(由 @jahnjw 贡献),用于<script>场景下释放全局Q名称:

// 在 <script> 标签中使用时 // 保存之前的全局 Q,然后挂载新的 Q var previousQ = global.Q; global.Q = definition(); global.Q.noConflict = function () { global.Q = previousQ; return this; };

这段逻辑在 q.js 中可以直接看到:noConflict会把全局Q恢复为加载前的值,并返回当前的 Q 对象(可通过return this得到引用继续使用)。需要注意的是,Q.noConflict只在 Q 作为全局变量使用时才有效,q.js 中定义了静态版本的兜底:在非全局环境下调用会抛出"Q.noConflict only works when Q is used as a global"错误。

1.3.0:Node.js 未处理/已处理拒绝追踪

1.3.0 在 Node.js 中加入了未处理(unhandled)与已处理(handled)拒绝的追踪(由 @benjamingr 贡献)。这其实是 0.9.4 引入的"未处理拒绝 API"的深化——现在 Q 会在拒绝被创建但从未被处理时,通过process.emit("unhandledRejection", reason, promise)通知 Node.js 运行时,并在拒绝稍后被处理时发出process.emit("rejectionHandled", ...)

源码实现位于 q.js 的"UNHANDLED REJECTION TRACKING"区块:

  • trackRejection(promise, reason):在Q.reject构造拒绝 promise 时被调用(见 q.js),把 promise 与 reason.stack 记入内部数组。
  • untrackRejection(promise):在拒绝被then的 rejection handler 消费时被调用(见 q.js)。
  • 配套的公开 API:Q.getUnhandledReasons()(返回当前未处理拒绝的堆栈快照副本,见 q.js)、Q.resetUnhandledRejections()(清空追踪状态)、Q.stopUnhandledRejectionTracking()(关闭追踪)。

1.2.x:Q.any诞生与 npm 打包瘦身

1.2.0引入了Q.any(promisesArray)方法(由 @vergara 贡献):返回一个 promise,该 promise 以promisesArray第一个被 fulfilled的 promise 的值作为 fulfillment 值;如果数组中所有 promise 都被拒绝,则返回被拒绝的 promise。这与all(全部 fulfilled 才成功、第一个拒绝即失败)和race(第一个 settle 者胜出)形成互补。

从 q.js 的实现可以看到细节:

  • 空数组输入时直接return Q.resolve()(fulfilled 的空值,不会拒绝);
  • 内部用array_reduce遍历,每个输入都挂上onFulfilled/onRejected/onProgress
  • 任何一个onFulfilled命中就deferred.resolve(result)定局;
  • 只有pendingCount归零(即全部被拒绝)才构造错误并deferred.reject
  • 进度通知以{index, value}形式透传,与Q.all的进度格式一致(见 q.js)。

1.1.2改为在 package.json 中使用"files"白名单(["LICENSE", "q.js", "queue.js"])替代.npmignore黑名单,从 npm 包里移除了多余文件(由 @anton-rudeshko 贡献)。这与 CHANGES.md 中 0.8.6 引入.npmignore的改动形成呼应:从"黑名单排除"到"白名单包含"的打包策略演进。

1.1.x:tapQ_DEBUGQ.nextTick可覆盖

1.1.0是一系列实用增强:

  • Q_DEBUG=1环境变量:在 Node.js 中设置Q_DEBUG=1即可全局开启长堆栈追踪。对应代码在 q.js:
if (typeof process === "object" && process && process.env && process.env.Q_DEBUG) { Q.longStackSupport = true; }
  • tap方法:promise 上的tap(callback)会在值经过时"看一眼"而不改变它,适合插入日志、埋点等副作用操作。定义见 q.js(Q.tapPromise.prototype.tap)。
  • instanceof识别自有 promise:Q 现在通过instanceof Promise判断"自己的" promise 实例,而不是把所有 thenable 都当作 Q promise(见 q.js 中Q(value)的分支逻辑)。
  • 超时错误带code === "ETIMEDOUT"Promise.prototype.timeout构造的错误对象会设置error.code = "ETIMEDOUT"(见 q.js),方便程序化判断超时原因。
  • Q.nextTick可被用户覆盖:允许使用者替换 Q 内部的任务调度实现。
  • 同时移除了 Node.js 0.6/0.8 的持续集成,原因是 npm 的^版本谓词运算符在传递依赖中无法在这些旧版本上使用。

1.1.1修复了启动(bootstrapping)过程中的两处回归:一处破坏了 WebWorker 支持,另一处完全破坏了<script>用法(issue #607)。这与模块加载封装(q.js)的多种环境检测逻辑直接相关。

1.0.x:Q.Promise与 CSP 兼容

1.0.1是 1.x 的重要里程碑:

  • Q.Promise构造器:实现 ES6Promise构造器的常用用法(new Q.Promise(function (resolve, reject, notify) {...}))。源码中Q.Promise = promise(q.js),其实现位于function promise(resolver)(q.js)。CHANGES.md 同时说明:"Promise并没有一个合法的 promise 构造器,真正的实现要等到 Q 2.0"——即在 1.x 中该构造器与 ES6 语义仍有差异,需注意使用前提。
  • 移除 promise inspector 的 console 兜底:该方案"已不再具备任何可靠性"。
  • CSP(内容安全策略)兼容:修复了禁止eval的环境。Q 改用StopIteration全局变量来区分 SpiderMonkey 生成器与 ES6 生成器(假设二者永不同时存在),相关分支逻辑可见 q.js 的async实现。

1.0.0本质是 0.9 的正式稳定化,仅有少量细节调整(例如浏览器中无论window.Touch是否定义都尝试输出调试信息;移除promise.valueOf的弃用警告,因为浏览器内部多种方式调用它,无法区分需要迁移的用法)。

0.9.x:API 清理与错误处理的分水岭

0.9.x 是 Q 历史上最重要的演进期,CHANGES.md 对这段历史的记录也最为详尽。

0.9.0:大规模 API 清理

0.9.0 移除了一层被弃用/未文档化的方法,使 Q 更贴近 Mark Miller 的 TC39 并发 strawman 提案。下表是 CHANGES.md 中的完整映射(原文表格,未做删减):

0.8.x 方法0.9 替代
Q.refQ
callapplybind(*)fcall/invokefapply/postfbind
ncallnapply(*)nfcall/ninvokenfapply/npost
enddone
putset
nodenbind
nendnodeify
isResolvedisPending
deferred.nodedeferred.makeNodeResolver
Methodsenderdispatcher
senddispatch
viewviewInfo(无)

(*)不鼓励使用thisp;调用方法时请用postinvoke

配套的 API 变更还包括:

  • Q(value)现在是一个可调用的导出函数,是resolve的别名(q.js 中Q.resolve = Q);
  • invoke在所有形态下都是send的别名(q.js 可看到Q.send/Q.mcall/Q.invoke的并排定义);
  • 不带方法名的post行为等同于fapply
  • deleteset(前身put)不再有 fulfillment 值;
  • Q promise 不再被冻结(因为冻结会损害 V8 性能);
  • 新增thenReject作为thenResolve的对偶方法。

错误处理方面:长堆栈可以通过Q.stackJumpLimit = 0关闭;在 Node.js 中,进程退出时若有未处理的拒绝,会输出到控制台。

内部协议(Internals and Advanced):内部 promise 接口改为dispatchPromise(resolve, op, operands),减少了参数切片;内部操作码 "put" 改为 "set"、"del" 改为 "delete";新增Q.fulfill,它与Q.resolve的区别在于不穿透 promise、也不强转其他系统的 promise——promise 本身就是 fulfillment 值,仅建议在"想用一个带有 then 函数但同时不是 promise 的对象去 fulfill 一个 promise"时使用。

0.9.5~0.9.7:状态检视与生成器支持

0.9.5引入了inspect——以{state: "fulfilled" | "rejected" | "pending", value | reason}形式获取 promise 状态(见 q.js 与 q.js 的 inspect 实现);allSettled则把输入 promises 全部 settle 后的状态数组化。CHANGES.md 特别澄清了术语:"settled"指 fulfilled 或 rejected;"resolved"指一个 deferred promise 被 resolve 到另一个 promise,即"把命运密封给后继 promise 的命运"。

同时:长堆栈默认关闭(需显式Q.longStackSupport = true);长堆栈可以追踪 promise 的完整异步历史而不仅是单次跳转;引入spawn(立即调用的异步生成器);增加实验性的mapplymcallnmapplynmcall方法调用同义词。

0.9.6修复了"inspect 实现之前的旧 Q promise 与兼容 Q promise 的识别"问题,并通过两条独立途径修复了无限异步强转循环:所有makePromise返回的 promise 都实现inspect(默认报告 "unknown" 状态);then/when的实现迁移到then,使强转职责集中在whennextTick也被重构为"无论请求多少个新 tick,都在 Q 内部使用展开的微任务"(issue #316,@rkatic)。

0.9.7宣布q.min.js不再入库(但仍由 Grunt 与 NPM 构建产出,见 Gruntfile.js);修复Q.async与 ES6 生成器的兼容问题;修复nextTick影响 Safari 6.0.5 首次加载(涉及 iframe)的问题;引入passByCopyjoinrace;控制台改为直接显示堆栈或错误消息而非Error对象;消除包装方法以提升性能;Q.all现在按 ES6 迭代的风格透传进度通知{value, index}

0.9.1~0.9.4:API 打磨与未处理拒绝追踪

  • 0.9.1:AMD 检测兼容 RequireJS 优化器的namespace选项(issue #225);修复valueOf的副作用——isFulfilledisRejectedisPending不再意外触发外部代码(issue #226)。
  • 0.9.2timeoutdelay现在会透传进度通知(issues #229、#229 相关);修复nbind真正绑定thisArg(issue #232)。
  • 0.9.3Q.timeout的错误支持自定义错误消息(issue #270);在 Node.js 0.10 中从process.nextTick切换到setImmediate,修复调用栈爆栈问题(issues #254、#259);修复与浏览器中 Mocha 测试运行器的兼容(Mocha 引入了没有nextTick的假process全局,issue #267);部分修复未处理拒绝检测的误报(issue #252);Q.promise在收到非函数参数时提前抛出异常。
  • 0.9.4isPromiseisPromiseAlike始终返回布尔值(issue #284);async支持 ES6 生成器(issue #288);清除 dispatch 方法中的重复拒绝(issue #238);引入未处理拒绝 API(issue #296,@domenic):stopUnhandledRejectionTrackinggetUnhandledReasonsresetUnhandledRejections——这正是 1.3.0 扩展的基础。

0.8.x:命名大调整与 Node 集成

0.8.x 是命名与 API 形态剧烈变动的时期,很多 0.9 时代的名字都源于此时。

  • 0.8.0:移除enqueue(用nextTick)、def(用master)、spy(用fin)、wait(用all(args).get(0))、join(用all(args).spread(callback));delay同时接受(value, timeout)(timeout)两种签名(该双签名逻辑延续至今,见 q.js);新增spread;新增defer().node()node/ncall(Node 风格回调适配)。
  • 0.8.3:promise 原型新增isFulfilledisRejectedisResolved;新增allResolved(等待所有 promise 要么 fulfilled 要么 rejected,不传播错误,issue #53);node导出改名nbindMethod改名sender;浏览器控制台输出未处理错误的实时列表;支持msSetImmediate/setImmediate作为浏览器端nextTick实验性别名finally(对应fin)、catch(对应fail)、try(对应call)、delete(对应del)。
  • 0.8.4promise.timeout的拒绝值从字符串改为Error对象,消息中包含超时毫秒数(CHANGES.md 明确标注这是一处"未文档化、未指定公共行为"的变更,可能影响依赖字符串异常的用户);新增deferred.makeNodeResolver()取代晦涩的deferred.node();新增实验性Q.promise(maker(resolve, reject))(受 @gozala 的流构造器模式与 Windows Metro Promise 构造器接口启发,这就是Q.Promise的前身)与Q.begin()
  • 0.8.5:初步支持长堆栈(@domenic);新增fapply/fcall/fbind无 thisp 版本;被拒绝的 promise 拥有exception属性;引入 Jasmine 规范(即本仓库的 spec/ 目录)。
  • 0.8.6:修复npost/ninvokethisp传递;修复多种非常规拒绝原因的处理;修复错误行为自定义 promise 的双重 resolve;加速Q.all(对已 resolve 的 promise 或标量值);堆栈过滤支持资源合并(issue #93);为弃用方法添加警告;添加.npmignore瘦身依赖包。
  • 0.8.9:新增nend;初步进度通知支持(then(onFulfilled, onRejected, onProgress)progress(onProgress)deferred.notify(...));put/del返回被操作对象以支持链式调用。
  • 0.8.10done作为end的替代(等价于then(f, r, p).end());Q.onerror可设置的错误陷阱(用于获取未捕获错误的完整堆栈);thenResolve快捷方式(issue #108);进度通知的透传与转换改进;nend改名nodeifydeferred.resolve/deferred.reject不再(有时)返回deferred.promise;修复spread在被拒绝 promise 上不调用拒绝处理器的缺陷。
  • 0.8.11:新增nfcall/nfapply/nfbindncall/napply/nbind弃用);长堆栈不再导致链式 promise 时内存线性增长(issue #111);拒绝处理器中检查error.stack现在能得到长堆栈(issue #103);修复Q.timeout在 promise 被拒绝时未清理定时器导致事件循环存活的缺陷(issue #145);新增q/queue模块(无限 promise 队列构造器,见 queue.js)。
  • 0.8.12:外部 promise 在Q.isFulfilled中被视为未 resolve,使Q.all可以处理含外部 promise 的数组(issue #154);修复与 Promises/A+ 规范及测试套件的小型不一致(issues #157、#158,对应仓库中的 spec/aplus-adapter.js)。

0.7.x 及更早:语源与根基

更早的版本记录了许多至今仍在影响 Q 形态的决策:

  • 0.7.0:移除reportasapfin的 callback 不再接收任何参数;enqueue改名nextTick;修复MessageChannel用于nextTick的 bug;shim 改为外部应用;实验性view/viewInfo
  • 0.6.0spy改名/别名finrefQ构造器形式导出;新增async装饰器(用yield来"蹦床" promise,即生成器支持的开端);whenall变为可链式。
  • 0.5.x:新增alljoin/wait重构为基于它,全部在最早拒绝处拒绝;failfinspy加入 Q 与 promise 原型;def改名master;浏览器优先用MessageChannel做 next tick;end结束 promise 链(让传播的拒绝被抛出,从而触发 Node 的uncaughtException与浏览器的onerror)。
  • 0.4.xend不再返回 promise;异常被消费后不再自动报告(必须显式通过.end().then(null, Q.error));支持 RequireJS;<script>场景创建Q全局变量。
  • 0.3.0post恢复为 Tyler Closeref_sendAPI 的原始双参数签名(第二个参数通常是参数数组),新invoke提供可变参数调用;Promise构造器改名makePromise;术语细化:isResolved改名isFulfilled,新isResolved表示"非 promise 或已 settled"。
  • 0.2.xpost(ref, name, args)改为可变参数(ref, name, ...args)(0.2.0 破坏性变更);thenable 采纳(0.2.6,使 Q 符合 Promises/A、B、D);promise 变为鸭子类型(promiseSend(op, resolved, ...)valueOf,0.2.5);拒绝也鸭子类型化(promiseRejected === "true"且带reason);新增q/queueq/util模块。
  • 0.0.x~0.1.x:初始版本与asap的引入/移除(0.1.0 因"坏了,大概坏在哲学上"而移除)。

这段历史揭示了 Q 的深层设计哲学:promise 不只是一个 API,而是一套可跨库交换的协议(鸭子类型、thenable 采纳、消息派发),这为后来 ES6 Promise 的标准化提供了实践基础。

用源码验证 1.x 核心 API 的当前形态

以下能力在 q.js 中均可找到对应实现,可作为阅读 CHANGES.md 时的对照索引:

CHANGES.md 提及的能力当前实现位置关键行为
Q.anyq.js空数组直接 resolve;首个 fulfilled 胜出;全部拒绝才 reject,错误信息含 "Q can't get fulfillment value from any promise"
Q.Promiseq.jsES6 风格(resolve, reject, notify)构造器,异常自动转为拒绝
未处理拒绝追踪q.jsunhandledRejection/rejectionHandled事件 +getUnhandledReasons/resetUnhandledRejections/stopUnhandledRejectionTracking
长堆栈支持q.jsQ.longStackSupport默认 false;Q_DEBUG=1环境变量自动开启;Q.stackJumpLimit = 0可关闭
tapq.js观察值经过而不改变
timeoutq.js拒绝值带code: "ETIMEDOUT";拒绝/完成时清理定时器
delayq.js双签名(value, ms)/(ms);拒绝立即透传
Node 适配q.jsnfapply/nfcall/denodeify/nbind/ninvoke/npost/nodeify+deferred.makeNodeResolver
生成器支持q.jsasync(生成器 → promise 蹦床)与spawn(立即执行并done
环境加载q.jsCommonJS / RequireJS / SES /<script>(含noConflict)多环境封装

配套测试集中在 spec/q-spec.js(Jasmine 行为规范)与 spec/aplus-adapter.js(Promises/A+ 符合性适配器),运行方式见 package.json 的 scripts:npm test(jasmine-node + promises-aplus-tests + jshint)与npm run test-browseropener spec/q-spec.html)。

小结:从 CHANGES.md 看 Q 的设计遗产

纵览 CHANGES.md,Q 的演进有四个反复出现的主题,至今仍具参考价值:

  1. 命名即文档resolve/fulfill/settle的术语区分(0.9.5)、isResolvedisFulfilled/isPending的细化(0.9.0、0.3.0),说明 API 命名需要精确承载语义。
  2. 错误必须可见:从end/done终止链、Q.onerror,到未处理拒绝追踪(0.9.4 → 1.3.0)与长堆栈(0.8.5 → 1.5.0 跨 rethrow),Q 一直在解决"异步错误被静默吞掉"这一核心痛点。
  3. 稳定与实验并存Q.PromiseQ.any等能力先以实验形态出现,再逐步稳定;同时大量实验性 API(viewsendernend)被及时清理。
  4. 协议优于实现:鸭子类型 promise、thenable 采纳、内部消息派发协议(0.2.x~0.9.x 一脉相承),让 Q 能与 jQuery、Dojo、When.js、WinJS 等互操作(README.md 的 Getting Started 一节有说明)。

对于现代开发者,CHANGES.md 的价值在于:它是理解"ES6 原生 Promise 之前"异步编程生态的第一手史料,也是阅读 q.js 源码时的路线图——每一个 1.x API 都能在这份变更日志里找到它的来龙去脉。

  • 异步编程

【免费下载链接】q

A promise library for JavaScript

项目地址:https://gitcode.com/gh_mirrors/q/q
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/21 15:55:11

Agent Harness Runtime 跑工具循环:Key 用 TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/21 15:54:10

Keysight E4980A LCR表TCP/SCPI远程控制实战指南

1. 这不是“远程控制”&#xff0c;而是让LCR表真正听懂你的指令Keysight E4980A LCR表&#xff0c;这台在电子元器件研发、产线测试、高校实验室里几乎人手一台的精密仪器&#xff0c;很多人用它测电容、电感、阻抗&#xff0c;却从没想过——它其实是个“沉默的TCP服务器”。…

作者头像 李华