- 后端
【免费下载链接】bluebird
:bird: :zap: Bluebird is a full featured promise library with unmatched performance.
导读
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");这里有三个关键实现细节:
- 消息缺省回退:
subError将message设置为传入的字符串;如果传入的不是字符串,则回退为默认消息。TimeoutError的默认消息是"timeout error"(即subError("TimeoutError", "timeout error")的第二个参数)。 name属性固定为"TimeoutError",且与message一样通过notEnumerableProp设置为不可枚举,保证错误序列化与遍历时的干净行为。- 继承关系:
inherits(SubError, Error)使TimeoutError是原生Error的子类,因此instanceof Error、e.stack、e.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还做了两件重要的事:
util.markAsOriginatingFromRejection(err)与promise._attachExtraTrace(err):将错误标记为"源于拒绝"并附加额外追踪信息,这是 Bluebird 长堆栈追踪(long stack traces)能力的一部分;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"); });要点拆解:
Promise.promisifyAll(require('fs'))把 Node 回调风格的fs变成 Promise 风格(readFileAsync),详见 promisification.md;.timeout(100)给读取操作加上 100ms 的时限;.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 Error与e 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 CancellationError:TimeoutError用于"时间限制到期",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.
相关推荐
es-toolkit TimeoutError 完全指南:超时错误的定义、抛出与捕获实践
es toolkit TimeoutError 完全指南:超时错误的定义、抛出与捕获实践 TimeoutError 是 es toolkit 提供的专用错误类,
前端后端Bluebird `.timeout()` 完全指南:为 Promise 设置超时上限与自定义超时错误
Bluebird .timeout 完全指南:为 Promise 设置超时上限与自定义超时错误 导读 .timeout 是 Bluebird 中用于给任意 Pr
后端Bluebird OperationalError 完全指南:显式拒绝错误的识别、捕获与源码原理
Bluebird OperationalError 完全指南:显式拒绝错误的识别、捕获与源码原理 导读 : OperationalError 是 Bluebir
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考