Bluebird TimeoutError 完全指南:超时错误的创建原理、`.timeout` 协作机制与实战捕获
2026/9/20 5:48:35 网站建设 项目流程
  • 后端

【免费下载链接】bluebird

:bird: :zap: Bluebird is a full featured promise library with unmatched performance.

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

导读

TimeoutError是 Bluebird 内置的专用错误类型,用于信号化"操作超时":当某个 Promise 在限定的毫秒数内既未完成也未失败时,Bluebird 的.timeout会以TimeoutError(或你指定的自定义错误)作为拒绝原因,将该 Promise 拒绝。本文以 timeouterror.md 为核心,结合 src/errors.js、src/timers.js、src/promise.js 与 test/mocha/timers.js 的源码与测试,为你讲透它的构造方式、默认消息、跨库副本一致性、与超时取消机制的底层协作,以及在实际代码中捕获与调试超时错误的完整方案。

TimeoutError 是什么

TimeoutError是 Bluebird 内置错误类型家族中的一员。Bluebird 提供了一组开箱即用的内置错误类型(详见 built-in-error-types.md),其中包括:

  • CancellationError:取消操作信号
  • TimeoutError:超时操作信号
  • OperationalError:可操作错误(如 I/O 失败)
  • AggregateError:聚合错误

其中TimeoutError的语义非常明确——它表示一个操作超出了允许的时间限制。它不携带业务数据,只负责告诉调用方"太慢了,我已经等不下去了"。

构造函数签名与消息规则

原文档给出的构造签名是:

new TimeoutError(String message) -> TimeoutError

构造函数接受一个字符串message作为第一个参数,该字符串会成为错误对象的.message属性。

从 src/errors.js 的源码可以看到,TimeoutError由统一的subError工厂函数创建:

function subError(nameProperty, defaultMessage) { function SubError(message) { if (!(this instanceof SubError)) return new SubError(message); notEnumerableProp(this, "message", typeof message === "string" ? message : defaultMessage); notEnumerableProp(this, "name", nameProperty); if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor); } else { Error.call(this); } } inherits(SubError, Error); return SubError; } var TimeoutError = subError("TimeoutError", "timeout error");

这里有三个关键实现细节:

  1. 消息缺省回退subErrormessage设置为传入的字符串;如果传入的不是字符串,则回退为默认消息。TimeoutError的默认消息是"timeout error"(即subError("TimeoutError", "timeout error")的第二个参数)。
  2. name属性固定为"TimeoutError",且与message一样通过notEnumerableProp设置为不可枚举,保证错误序列化与遍历时的干净行为。
  3. 继承关系inherits(SubError, Error)使TimeoutError是原生Error的子类,因此instanceof Errore.stacke.message等原生行为全部可用;在有Error.captureStackTrace的环境(现代 V8)下会捕获完整堆栈。

注意工厂函数中的容错写法if (!(this instanceof SubError)) return new SubError(message);,这意味着即使你忘了new关键字直接调用TimeoutError("msg"),它也会返回一个正常的TimeoutError实例。

默认消息常量

在 src/constants.js 中定义了超时默认消息常量:

CONSTANT(TIMEOUT_ERROR, "operation timed out");

这就是当你使用.timeout(ms)且不提供任何自定义消息时,最终注入到TimeoutError中的消息文本"operation timed out"(与构造函数自身的默认值"timeout error"是两套机制,前者由 src/timers.js 显式传入,后者仅是构造时的兜底)。

如何获取 TimeoutError 引用

TimeoutError不是全局变量,需要通过 Promise 构造函数访问。在 src/promise.js 中完成挂载:

Promise.TimeoutError = errors.TimeoutError;

因此典型获取方式为:

var Promise = require("bluebird"); var TimeoutError = Promise.TimeoutError;

文档还特别指出:所有内置错误类型在 Bluebird 的多个副本之间保持同一身份。这一点在 src/errors.js 中有专门实现——Bluebird 会将错误类型挂到Error[BLUEBIRD_ERRORS]这个不可写、不可枚举、不可配置的冻结对象上,如果某个副本发现该属性已存在,就直接复用已有类型而不是重新创建:

var errorTypes = Error[BLUEBIRD_ERRORS]; if (!errorTypes) { errorTypes = Objectfreeze({ CancellationError: CancellationError, TimeoutError: TimeoutError, OperationalError: OperationalError, RejectionError: OperationalError, AggregateError: AggregateError }); es5.defineProperty(Error, BLUEBIRD_ERRORS, { value: errorTypes, writable: false, enumerable: false, configurable: false }); }

这意味着即使项目中同时加载了多个 Bluebird 副本(例如通过不同依赖间接引入),你在.catch(Promise.TimeoutError, ...)中的类型匹配依然可靠,不会因为"此副本的 TimeoutError 非彼副本的 TimeoutError"而漏捕。相应的多副本一致性测试可参考 test/mocha/multiple-copies.js 与 test/mocha/bluebird-multiple-instances.js。

TimeoutError 与.timeout的协作机制

原文档明确:TimeoutError被用作.timeout的自定义取消原因。先看.timeout的 API:

.timeout( int ms, [String message="operation timed out"] ) -> Promise
.timeout( int ms, [Error error] ) -> Promise

两种签名分别允许:

  • 字符串自定义错误消息;
  • 一个 Error 实例作为完整的拒绝原因(此时不再创建TimeoutError)。

底层实现:afterTimeout

在 src/timers.js 中,超时触发时的核心逻辑是afterTimeout

var afterTimeout = function (promise, message, parent) { var err; if (typeof message !== "string") { if (message instanceof Error) { err = message; } else { err = new TimeoutError(TIMEOUT_ERROR); } } else { err = new TimeoutError(message); } util.markAsOriginatingFromRejection(err); promise._attachExtraTrace(err); promise._reject(err); if (parent != null) { parent.cancel(); } };

可以看到完整的分支决策:

调用方式message 类型拒绝原因
.timeout(100)未传(undefined)new TimeoutError("operation timed out")
.timeout(100, "custom message")字符串new TimeoutError("custom message")
.timeout(100, someError)Error 实例直接使用someError本身,不创建TimeoutError

此外,afterTimeout还做了两件重要的事:

  1. util.markAsOriginatingFromRejection(err)promise._attachExtraTrace(err):将错误标记为"源于拒绝"并附加额外追踪信息,这是 Bluebird 长堆栈追踪(long stack traces)能力的一部分;
  2. parent.cancel():超时发生后,会尝试取消父 Promise——这正是TimeoutError被称为"自定义取消原因"的原因。不过取消行为受 Promise.config 的cancellation选项控制(详见下文)。

计时与清理:HandleWrapper

Promise.prototype.timeout(src/timers.js)通过setTimeout建立计时器,并用HandleWrapper包装句柄:

var handleWrapper = new HandleWrapper(setTimeout(function timeoutTimeout() { if (ret.isPending()) { afterTimeout(ret, message, parent); } }, ms));
  • 若原 Promise 在ms毫秒内先完成/失败,则通过successClear/failureClear回调clearTimeout掉计时器,避免泄漏(src/timers.js);
  • 若在超时时刻原 Promise 仍处于 pending(ret.isPending()为真),才触发afterTimeout
  • 启用 cancellation 时,ret._setOnCancel(handleWrapper)保证取消该派生 Promise 时同步清掉计时器,而HandleWrapper.prototype._resultCancelled负责真正的clearTimeout(src/timers.js)。

对应的清理行为在 test/mocha/timers.js 中有专门测试:无论 Promise 最终 fulfilled 还是 rejected,都会以正确的句柄类型调用clearTimeout

实战:如何在代码中捕获 TimeoutError

原文档给出的经典示例(文件读取超时场景):

var Promise = require("bluebird"); var fs = Promise.promisifyAll(require('fs')); fs.readFileAsync("huge-file.txt").timeout(100).then(function(fileContents) { // 100ms 内读取完成 }).catch(Promise.TimeoutError, function(e) { console.log("could not read file within 100ms"); });

要点拆解:

  1. Promise.promisifyAll(require('fs'))把 Node 回调风格的fs变成 Promise 风格(readFileAsync),详见 promisification.md;
  2. .timeout(100)给读取操作加上 100ms 的时限;
  3. .catch(Promise.TimeoutError, ...)是 Bluebird 的谓词捕获:只有拒绝原因是TimeoutError实例时才会进入该分支,其他错误会继续往下传播。关于谓词捕获语法可参考 catch.md。

自定义消息与自定义错误

// 自定义消息 somePromise.timeout(2000, "数据库查询超过 2 秒").catch(Promise.TimeoutError, function(e) { console.log(e.message); // "数据库查询超过 2 秒" }); // 完全自定义错误对象(不再创建 TimeoutError) var customError = new Error("gateway timeout"); somePromise.timeout(2000, customError).caught(function(e) { assert(e === customError); // true });

第二条用法在 test/mocha/timers.js 中有测试验证:当传入 Error 实例时,捕获到的就是同一个对象。

超时与取消的联动

启用 cancellation 后(Promise.config({cancellation: true})),一旦超时发生,父 Promise 会被取消,其后续的.then回调不会再执行。测试 test/mocha/timers.js 验证了:

  • 单个消费者场景下,超时后父 Promise 被取消,其后的回调不会执行;
  • 存在多个消费者时(如p被多处.then使用),父 Promise 不会被取消,避免误伤其他消费方。
Promise.config({cancellation: true}); var p = slowOperation(); // 22ms 后才完成 p.timeout(11).thenReturn(10).catch(Promise.TimeoutError, function(e) { // 11ms 超时,p 被取消,slowOperation 的后续逻辑不再执行 });

捕获超时后如何区分与调试

TimeoutError的实例具备以下可观测属性(由 src/errors.js 的实现保证):

  • e.name === "TimeoutError"
  • e.message:默认"operation timed out"(由 src/constants.js 的TIMEOUT_ERROR常量注入)或你指定的自定义消息;
  • e instanceof Errore instanceof Promise.TimeoutError均为true
  • 在支持Error.captureStackTrace的环境中带有完整堆栈,且经过_attachExtraTrace附加了异步调用链信息,便于定位"是哪一个调用点上的超时"。

因此调试时可以直接打印:

promise.timeout(500).catch(Promise.TimeoutError, function(e) { console.error("超时:", e.name, "-", e.message); console.error(e.stack); });

与相近概念的关系

  • TimeoutErrorvs 普通Error.timeout的第二参数传入Error实例时,拒绝原因就是该实例本身,此时无法用Promise.TimeoutError谓词捕获;只有未传或传字符串时才会产生TimeoutError
  • TimeoutErrorvs CancellationErrorTimeoutError用于"时间限制到期",CancellationError用于"主动取消";但超时发生时会调用parent.cancel(),因此启用 cancellation 时超时往往会连锁触发父链路上的取消信号。
  • 跨库副本一致性TimeoutError等错误类型被冻结挂载在Error[BLUEBIRD_ERRORS]上(src/errors.js),保证多副本场景下谓词捕获依然有效。

小结

TimeoutError是 Bluebird 处理"操作超时"这一核心异常场景的标准信号:

  • 构造方式为new TimeoutError(String message)name恒为"TimeoutError",缺省消息为"timeout error"(src/errors.js);
  • .timeout(ms, message?)中,未指定或指定字符串时产生TimeoutError,默认消息为"operation timed out",指定 Error 实例则直接复用该实例(src/timers.js);
  • 通过Promise.TimeoutError引用,配合.catch(Promise.TimeoutError, ...)谓词捕获实现精确、优雅的超时错误处理;
  • 超时会触发父 Promise 取消(受 cancellation 开关控制),并有完善的计时器清理机制防止泄漏。

掌握TimeoutError,你就掌握了 Bluebird 超时控制这一高频场景的完整链路:从错误构造、默认消息,到.timeout的内部协作,再到谓词捕获与取消联动,全部可以在 docs/docs/api/timeouterror.md 的 API 定义之上,通过 src/errors.js 与 src/timers.js 的源码以及 test/mocha/timers.js 的测试用例获得代码级验证。

  • 后端

【免费下载链接】bluebird

:bird: :zap: Bluebird is a full featured promise library with unmatched performance.

项目地址:https://gitcode.com/gh_mirrors/bl/bluebird
点击查看免费下载
上一篇:CANN/Ascend C SIMT线程组划分
下一篇:PersonaLive直播案例:虚拟偶像如何用AI实现表情同步

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询