
core-js 中的 ECMAScript Explicit Resource ManagementDisposableStack 与 AsyncDisposableStack 全解析【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js本文以 core-js 官方文档 docs/web/docs/features/ecmascript/explicit-resource-management.md 为骨架深入解读 core-js 对 ECMAScript 显式资源管理提案Explicit Resource ManagementTC39 Stage 3的内置对象实现DisposableStack、AsyncDisposableStack、SuppressedError以及 Iterator 的dispose/asyncDispose方法。读完本文你将掌握这些 API 的完整签名、LIFO 释放语义、错误抑制机制、异步释放流程以及如何在项目中通过 core-js 的 entry points 按需引入这些 polyfill并理解其底层源码与单元测试的实现细节。一、背景using语法与 core-js 的职责边界显式资源管理提案tc39/proposal-explicit-resource-management为 JavaScript 引入了资源生命周期管理的标准化机制目标是解决文件句柄、数据库连接、定时器等资源“忘记释放”导致泄漏的问题。提案由两大部分组成语法部分using/await using声明由转译器如 Babel负责处理运行时内建部分Symbol.dispose、Symbol.asyncDispose、DisposableStack、AsyncDisposableStack、SuppressedError等内置对象由运行时或 polyfill 提供。core-js 官方文档特别强调了一个关键边界注意core-js 只提供该提案的**内置对象built-ins**部分using语法支持需要转译器支持如 Babel 的 explicit-resource-management 语法插件。也就是说使用 core-js 之后Symbol.dispose、DisposableStack等运行时对象可用但using关键字本身仍需通过 Babel 等工具转译为对上述内置对象的调用。在 core-js 的源码中这一点体现为模块按 TC39 提案路径组织例如 es.disposable-stack.constructor.js 文件头部直接标注了提案链接同时针对异步版本另有一个独立的提案 tc39/proposal-async-explicit-resource-management。二、模块清单四个核心 polyfill 模块文档中列出本特性涉及以下 4 个模块均位于 packages/core-js/modules/ 下模块文件提供的内置对象es.disposable-stack.constructor.js同步DisposableStackes.iterator.dispose.jsIterator.prototype[Symbol.dispose]es.async-disposable-stack.constructor.js异步AsyncDisposableStackes.async-iterator.async-dispose.jsAsyncIterator.prototype[Symbol.asyncDispose]除此之外SuppressedError在文档签名中作为关键组成部分出现其实现位于独立的 es.suppressed-error.constructor.js 模块。配套的单元测试位于 tests/unit-global/ 目录下es.disposable-stack.constructor.js、es.async-disposable-stack.constructor.js、es.iterator.dispose.js、es.async-iterator.async-dispose.js、es.suppressed-error.constructor.js同时 tests/unit-pure/ 中也有对应的 pure 版本测试。三、内置对象完整签名以下是文档给出的 TypeScript 签名完整保留作为后续各节的对照基准class Symbol { static asyncDispose: asyncDispose; static dispose: dispose; } class DisposableStack { constructor(): DisposableStack; dispose(): undefined; use(value: Disposable): value; adopt(value: object, onDispose: Function): value; defer(onDispose: Function): undefined; move(): DisposableStack; dispose(): undefined; toStringTag: DisposableStack; } class AsyncDisposableStack { constructor(): AsyncDisposableStack; disposeAsync(): Promiseundefined; use(value: AsyncDisposable | Disposable): value; adopt(value: object, onDispose: Function): value; defer(onDispose: Function): undefined; move(): AsyncDisposableStack; asyncDispose(): Promiseundefined; toStringTag: AsyncDisposableStack; } class SuppressedError extends Error { constructor(error: any, suppressed: any, message?: string): SuppressedError; error: any; suppressed: any; message: string; cause: any; } class Iterator { dispose(): undefined; } class AsyncIterator { asyncDispose(): Promiseundefined; }要点归纳DisposableStack/AsyncDisposableStack均为构造器类需要new调用单元测试 es.disposable-stack.constructor.js 中assert.throws(() DisposableStack(), ...)验证了不带new会抛错二者共享use/adopt/defer/move四个方法差异在于同步栈的dispose()与异步栈的disposeAsync()AsyncDisposableStack.use接受AsyncDisposable | Disposable即可以同时容纳同步与异步可释放资源类本身实现了dispose/asyncDispose方法因此它们自身也是可释放对象可被外层using或另一个栈托管。四、深入DisposableStackLIFO 释放与错误抑制4.1 内部状态机从 es.disposable-stack.constructor.js 源码可以看到DisposableStack内部通过InternalStateModule维护状态state字段取值PENDING等待中或DISPOSED已释放stack字段是一个数组存放已注册的释放函数当支持属性描述符DESCRIPTORS时disposed通过访问器暴露为state DISPOSED在不支持描述符的环境中退化为实例上的布尔属性this.disposed false/ true。var getPendingDisposableStackInternalState function (stack) { var internalState getDisposableStackInternalState(stack); if (internalState.state DISPOSED) throw new $ReferenceError(DISPOSABLE_STACK already disposed); return internalState; };任何需要修改栈的方法use、adopt、defer、move都会先经过getPendingDisposableStackInternalState一旦栈已释放会抛出ReferenceError(DisposableStack already disposed)。4.2dispose()LIFO 顺序与错误链dispose()的实现体现了两个核心语义1后进先出LIFO。释放时从栈顶数组末尾向前遍历var stack internalState.stack; var i stack.length; while (i) { var disposeMethod stack[--i]; stack[i] null; try { disposeMethod(); } catch (errorResult) { /* ... */ } }单元测试 es.disposable-stack.constructor.js 中的DisposableStack综合用例验证了这一点先use的释放函数输出6、后use的输出3最终结果字符串依次叠加确认了逆序LIFO调用顺序。2错误抑制链SuppressedError。若释放过程中某个函数抛出异常dispose()会继续释放剩余资源同时把多个异常包装为SuppressedError链var thrown false; var suppressed; // ... } catch (errorResult) { if (thrown) { suppressed new SuppressedError(errorResult, suppressed); } else { thrown true; suppressed errorResult; } } // ... if (thrown) throw suppressed;第一次捕获的异常作为suppressed后续每个新异常errorResult都包一层new SuppressedError(errorResult, suppressed)形成嵌套链——这保证了“一个资源释放失败其他资源仍然会被释放”符合提案的容错语义。4.3use/adopt/defer与addDisposableResource三个方法都委托给内部抽象操作addDisposableResource见 packages/core-js/internals/add-disposable-resource.js其实现对应提案规范中的GetDisposeMethod/CreateDisposableResource/AddDisposableResource三个抽象操作use(value)传入一个实现了Symbol.dispose的对象。内部先通过getDisposeMethod(value, sync-dispose)取出dispose方法再bind到该对象上压入栈最后原样返回value方便链式使用adopt(value, onDispose)资源本身不实现Symbol.dispose而是由调用方显式传入释放回调回调以value为参数执行onDispose(value)defer(onDispose)仅注册一个延迟执行的释放回调资源值为undefined。addDisposableResource中有一个值得注意的细节当V为null/undefined且 hint 为sync-dispose时直接返回不注册而async-dispose场景下则会记录求值行为以确保后续释放时执行Await。另外async-disposehint 下GetDisposeMethod的查找顺序是先找Symbol.asyncDispose找不到再回退到Symbol.dispose并把同步方法包装进Promise——这正是异步栈可以接纳同步Disposable对象的底层原因。4.4move()转移释放责任move()创建一个新的DisposableStack把当前栈的资源数组整体移交过去随后将原栈标记为DISPOSED原栈变为空壳再次使用会抛ReferenceErrorvar newDisposableStack new $DisposableStack(); getDisposableStackInternalState(newDisposableStack).stack internalState.stack; internalState.stack []; internalState.state DISPOSED; return newDisposableStack;4.5dispose别名与toStringTag模块末尾将dispose指向dispose方法保证stack[Symbol.dispose] stack.dispose并设置Symbol.toStringTag为DisposableStack。单元测试 es.disposable-stack.constructor.js 中assert.same(DisposableStack.prototype[Symbol.dispose], DisposableStack.prototype.dispose)与assert.same(DisposableStack.prototype[Symbol.toStringTag], DisposableStack)直接验证了这两点。五、深入AsyncDisposableStack串行异步释放es.async-disposable-stack.constructor.js 的实现与同步版本结构相似但有两个显著差异5.1disposeAsync()返回 Promise 并串行释放disposeAsync()返回一个Promise内部通过loop()递归串行处理栈中的每个释放函数每个函数的结果都用Promise.resolve(...).then(loop, handleError)统一为 Promise 并等待完成确保异步释放按注册逆序逐个执行完毕错误同样通过handleError累积为SuppressedError链全部完成后thrown ? reject(suppressed) : resolve(undefined)。try { Promise.resolve(disposeMethod()).then(loop, handleError); } catch (error) { handleError(error); }同步抛错与异步 reject 都被handleError统一收纳体现了对“混合同步/异步释放函数”的兼容。5.2 V8 特定 bug 的强制覆盖模块底部有一段值得注意的兼容性逻辑// https://github.com/tc39/proposal-explicit-resource-management/issues/256 // cant be detected synchronously var SYNC_DISPOSE_RETURNING_PROMISE_RESOLUTION_BUG V8_VERSION V8_VERSION 136; $({ global: true, constructor: true, forced: SYNC_DISPOSE_RETURNING_PROMISE_RESOLUTION_BUG }, { AsyncDisposableStack: $AsyncDisposableStack });当运行在 V8 版本低于 136 的引擎上时core-js 会**强制覆盖forced**原生AsyncDisposableStack以规避“同步dispose返回 Promise 时的解析顺序”问题。这也是AsyncDisposableStack#use的单元测试tests/unit-global/es.async-disposable-stack.constructor.js中会检查同步资源仅实现Symbol.dispose也能被异步栈正常托管并串行释放的原因。5.3 与同步栈一致的四个注册方法use/adopt/defer/move语义与同步版本一致区别仅在于 hint 变为async-dispose且adopt的释放回调会被Promise.resolve承接adopt用例中验证了回调以资源为参数、this为undefined严格模式下、参数个数为 1 等细节。同样地AsyncDisposableStack.prototype[Symbol.asyncDispose]与disposeAsync等价Symbol.toStringTag为AsyncDisposableStack。六、SuppressedError抑制异常的载体SuppressedError继承自Error用于承载资源释放过程中被“抑制”的异常字段为error本次释放抛出的错误与suppressed此前被抑制的错误可选message并支持cause。es.suppressed-error.constructor.js 的源码显示core-js 只有在原生实现存在缺陷时才打补丁forced: PATCHWRONG_ARITY原生构造器length ! 3针对 Bun 的 issue 9282EXTRA_ARGS_SUPPORTnew SuppressedError(1, 2, 3, { cause: 4 }).cause ! 4针对 Bun 的 issue 9283。当需要打补丁时core-js 通过setPrototypeOf让SuppressedError继承Error并将error、suppressed创建为非枚举属性。单元测试 es.suppressed-error.constructor.js 验证了其arity为 3、name为SuppressedError。注意该构造函数同时被同步与异步两个栈的释放逻辑复用通过getBuiltIn(SuppressedError)获取。七、Iterator 的dispose与asyncDispose为了让for...of/ 迭代器对象也能参与显式资源管理提案为迭代器增加了释放钩子Iterator.prototype[Symbol.dispose]es.iterator.dispose.js——仅当迭代器自身实现了return方法时调用它if (!hasOwn(IteratorPrototype, DISPOSE)) { defineBuiltIn(IteratorPrototype, DISPOSE, function () { var $return getMethod(this, return); if ($return) call($return, this); }); }AsyncIterator.prototype[Symbol.asyncDispose]es.async-iterator.async-dispose.js——返回 Promise将return的调用结果统一包装defineBuiltIn(AsyncIteratorPrototype, ASYNC_DISPOSE, function () { var O this; return new Promise(function (resolve, reject) { var $return getMethod(O, return); if ($return) { Promise.resolve(call($return, O)).then(function () { resolve(undefined); }, reject); } else resolve(undefined); }); });对应的单元测试tests/unit-global/es.iterator.dispose.js、tests/unit-global/es.async-iterator.async-dispose.js验证了无return方法时释放结果恒为undefined有return时会被以迭代器自身为this调用且返回的任意值如示例中的7都会被吞掉最终返回undefined。八、Entry Points如何按需引入core-js 文档给出的入口entry points规范如下支持core-js与core-js-pure两个发行版且均可从es、stable、actual、full四个层级引入core-js(-pure)/es|stable|actual|full/disposable-stack core-js(-pure)/es|stable|actual|full/async-disposable-stack core-js(-pure)/es|stable|actual|full/iterator/dispose core-js(-pure)/es|stable|actual|full/async-iterator/async-dispose实际操作示例// 按需引入推荐体积最小 import core-js/es/disposable-stack; import core-js/es/async-disposable-stack; import core-js/es/iterator/dispose; import core-js/es/async-iterator/async-dispose; // 或使用 stable 层级仅含已稳定的提案 import core-js/stable/disposable-stack;其中es层级对应 packages/core-js/es/ 目录、stable对应 packages/core-js/stable/ 目录、actual对应 packages/core-js/actual/ 目录、full对应 packages/core-js/full/ 目录core-js-pure版本则不污染全局从 packages/core-js-pure/override/ 导出对应实现。这些模块同时挂在同步/异步栈的入口下SuppressedError与迭代器释放方法则由es.suppressed-error.constructor.js、es.iterator.dispose.js、es.async-iterator.async-dispose.js分别提供完整测试见 tests/unit-global/ 与 tests/unit-pure/ 同名文件。九、实战示例文件资源与异步连接管理综合以上 API一个典型的同步文件资源管理示例配合 Babel 的using转译后等价于手写调用import core-js/es/disposable-stack; import core-js/es/suppressed-error; const stack new DisposableStack(); // use托管实现了 Symbol.dispose 的资源 stack.use({ [Symbol.dispose]() { console.log(close file); } }); // adopt资源本身不实现 dispose显式给出释放回调 const buffer new ArrayBuffer(8); stack.adopt(buffer, (buf) console.log(release buffer, buf.byteLength)); // defer注册一段延迟执行的清理逻辑 stack.defer(() console.log(flush logs)); // dispose 时按 LIFO 逆序执行flush logs → release buffer → close file stack.dispose(); console.log(stack.disposed); // true异步场景例如管理数据库连接与游标import core-js/es/async-disposable-stack; import core-js/es/async-iterator/async-dispose; async function main() { const stack new AsyncDisposableStack(); const connection { async [Symbol.asyncDispose]() { await conn.close(); } }; stack.use(connection); // 同步资源也可以被异步栈托管底层自动包装为 Promise stack.use({ [Symbol.dispose]() { console.log(sync cleanup); } }); await stack.disposeAsync(); // 串行逆序执行所有释放逻辑 } main();十、总结围绕 core-js 对显式资源管理提案的内置对象实现本文完整呈现了官方文档定义的模块清单、TypeScript 签名与 entry points并结合 packages/core-js/modules/ 下的源码与 tests/unit-global/ 下的单元测试从实现层面解读了DisposableStack的 PENDING/DISPOSED 状态机、LIFO 释放、SuppressedError错误链与move()责任转移AsyncDisposableStack基于 Promise 的串行异步释放、对同步Disposable的回退包装以及针对 V8 136 的强制覆盖策略SuppressedError的条件打补丁逻辑Bun 的 arity 与cause缺陷迭代器dispose/asyncDispose对return方法的委托语义。需要再次强调的是core-js 提供的是运行时内建对象using语法本身仍需借助 Babel 的 explicit-resource-management 语法插件 转译。将两者结合即可在现代与老旧 JavaScript 引擎上统一获得标准化的显式资源管理能力。【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考