Strict Enforcement of 'using' S1
中文标题:严格强制使用
using
- 阶段: Stage 1
- 状态: 进行中
- ECMAScript 版本: —
- 同步时间: 2026年8月26日
- English original · 官方仓库
该提案通过新的 Symbol.enter 引入了一种可选的严格资源管理强制机制,确保资源必须通过 'using' 或 'await using' 声明使用,或附加到 DisposableStack/AsyncDisposableStack。它增加了在资源注册时调用 Symbol.enter 的语义,返回实际的可释放对象。
以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。
严格强制使用 using
一个提案,旨在扩展显式资源管理,通过 Symbol.enter 为资源添加一个可选的严格 using 使用要求。
这是一个后续提案,最初记录在 tc39/proposal-explicit-resource-management#195 中。其目的是添加一种可选的机制,以强制实施更严格的资源管理模型,迫使用户对资源使用 using、await using、DisposableStack 和 AsyncDisposableStack。
状态
阶段: 1
提案发起人: Ron Buckton (@rbuckton)
有关更多信息,请参阅 TC39 提案流程。
作者
- Ron Buckton (@rbuckton)
动机
显式资源管理 提案中当前的资源管理模型允许创建和使用 Disposables——具有 [Symbol.dispose]() 方法的对象,其生命周期与 using 声明相结合,绑定到包含块作用域。
当前提案的一个注意事项是,无法强制将 Disposable 分配给 using 声明,或附加到 DisposableStack 对象。因此,用户可以自由构造 Disposable 而不进行注册,并忽略手动执行清理。
主提案中未包含强制机制,以促进采纳。最初,预计最大的可释放资源来源可能是宿主 API,例如 DOM、NodeJS 或 Electron 中的 API。这些宿主已经拥有 现有 API,可以轻松适应以支持 [Symbol.dispose]() 或 [Symbol.asyncDispose]()。如果此类强制是强制性的,这些宿主将需要实现和维护一套完全并行的 API,纯粹为了支持 using。因此,我们将其作为纯粹可选的机制提出,可由需要更强强制保证的新 API 利用。
提出的解决方案
我们提议采用额外的资源管理符号——Symbol.enter——以及为 using、await using、DisposableStack.prototype.use 和 AsyncDisposableStack.prototype.use 添加的额外语义。
using 和 await using 声明
当通过 using 或 await using 声明资源时,我们首先检查资源是否具有 [Symbol.enter]() 方法。如果存在此类方法,则将调用它(在这两种情况下都是同步调用),其结果将成为实际绑定的值,并且其 [Symbol.dispose]()(或 [Symbol.asyncDispose]())方法被注册。
本提案在 [Symbol.enter]() 的目的上不区分 using 和 await using,因为 [Symbol.enter]() 方法不能是异步的。它的功能纯粹是一个强制守卫,并非旨在延迟初始化。
DisposableStack 和 AsyncDisposableStack
DisposableStack.prototype.use() 和 AsyncDisposableStack.prototype.use() 的功能与其语法对应项类似。如果提供的资源具有 [Symbol.enter]() 方法,则会调用它,其结果是通过 use() 方法注册和返回的实际资源。
Symbol.enter 如何实现严格强制?
Symbol.enter 通过在资源获取和使用之间引入额外步骤来实现严格强制。严格强制包装对象本身不实现任何特定于资源的功能。相反,它通过直接调用 [Symbol.enter]() 来引导用户使用 using,将其作为手动解包的更方便的替代方案。
是什么使其成为“可选”功能?
如果 显式资源管理 提案仅以严格强制作为唯一选项发布,那么宿主将需要要么将 API 表面分化,引入现有 API 的新严格强制版本,要么在现有 API 中添加额外的 [Symbol.enter]() { return this; } 以符合要求。
我们认为 using 采纳的最可能驱动因素将是宿主 API 采用的直接结果。我们期望宿主更有可能选择一种为现有开发人员提供最小阻力路径的方法,以促进采纳,因此期望 [Symbol.enter]() { return this; } 是提供最小阻力的方法。
许多宿主 API 已经使用在垃圾回收期间执行的终结器来保护本地资源。例如,NodeJS 中的 fs.promises.FileHandle 如果 FileHandle 实例被垃圾回收,将自动释放其底层文件句柄。反过来说,许多用户生成的可释放对象可能仅包含由 GC 释放的内存管理对象,由宿主 API 提供的本地资源组成,或两者的组合。由于这些因素,严格强制并非完全必要。
因此,本提案将 Symbol.enter 引入为纯粹可选的机制。当对象上不存在它时,运行时就像存在隐式的 [Symbol.enter]() { return this; }。这与宿主可能对现有 API 采取的“最小阻力路径”方法一致,而无需此类方法的额外开销。它还允许库或包作者自主地要求严格强制,如果其 API 需要的话。
替代方案
如果没有 Symbol.enter,API 作者可以考虑两种替代方案作为严格强制的潜在解决方法:
- 利用
FinalizationRegistry在资源被垃圾回收而非释放时向控制台发出警告。 - 将
[Symbol.dispose]定义为 getter 而不是方法。
解决方法:FinalizationRegistry
在此方法中,我们使用 FinalizationRegistry 来警告用户未正确清理资源。
这里,分配 Resource 会向终结注册表添加条目,并使用自身作为注销令牌。当调用 [Symbol.dispose]() 方法时,Resource 将从终结注册表中移除。如果垃圾回收前未调用 [Symbol.dispose]() 方法,终结注册表的清理回调将触发向控制台发出警告。
解决方法:Symbol.dispose Getter
在此方法中,我们将 [Symbol.dispose] 定义为 getter 而不是方法。这使我们可以防止资源在无效状态下被使用,假设直接访问 [Symbol.dispose] 足以表明资源注册已发生。
这里,Resource 最初构造在"未注册"状态。对资源执行操作的尝试受到 #throwIfInvalid() 调用的保护,该调用检查资源是否已注册以及资源是否已释放。由于 [Symbol.dispose] 是 getter,访问 getter 会触发状态从"未注册"更改为"已注册",并返回实际释放方法,该方法将由 using 在包含块结束时调用。
未提出的内容
我们明确不提出具有 Python 的 上下文管理器 广度功能的特性。上下文管理器允许你拦截、替换甚至丢弃上下文内抛出的异常,这比所提议的机制复杂得多。有关完整异常处理的进一步讨论,请参阅 tc39/proposal-explicit-resource-management#49。
先前技术
相关提案
潜在语义
CreateDisposableResource ( V, hint, method ) 将被修改如下:
API
本提案将引入内置符号 @@enter,作为 Symbol 构造函数上名为 enter 的静态属性。
待办事项
以下是通过 TC39 提案流程 各阶段推进的高级任务列表: