For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/stage/1/proposal-using-enforcement.md.
  • 简体中文
  • Strict Enforcement of 'using' S1

    中文标题:严格强制使用 using

    提案概览
    提案速览

    该提案通过新的 Symbol.enter 引入了一种可选的严格资源管理强制机制,确保资源必须通过 'using' 或 'await using' 声明使用,或附加到 DisposableStack/AsyncDisposableStack。它增加了在资源注册时调用 Symbol.enter 的语义,返回实际的可释放对象。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    严格强制使用 using

    一个提案,旨在扩展显式资源管理,通过 Symbol.enter 为资源添加一个可选的严格 using 使用要求。

    这是一个后续提案,最初记录在 tc39/proposal-explicit-resource-management#195 中。其目的是添加一种可选的机制,以强制实施更严格的资源管理模型,迫使用户对资源使用 usingawait usingDisposableStackAsyncDisposableStack

    状态

    阶段: 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——以及为 usingawait usingDisposableStack.prototype.useAsyncDisposableStack.prototype.use 添加的额外语义。

    usingawait using 声明

    当通过 usingawait using 声明资源时,我们首先检查资源是否具有 [Symbol.enter]() 方法。如果存在此类方法,则将调用它(在这两种情况下都是同步调用),其结果将成为实际绑定的值,并且其 [Symbol.dispose]()(或 [Symbol.asyncDispose]())方法被注册。

    function getStrictResource() {
      // 构造实际资源。
      const actualResource = {
        value: "resource",
        [Symbol.dispose]() { }
      };
    
      // 构造并返回一个资源包装器,强制实施严格语义。
      return {
        [Symbol.enter]() {
          return actualResource;
        }
      };
    }
    
    {
      using res = getStrictResource(); // 调用 retval[Symbol.enter]()。
      res.value; // "resource"
    
    } // 调用 res[Symbol.dispose]()。

    本提案在 [Symbol.enter]() 的目的上不区分 usingawait using,因为 [Symbol.enter]() 方法不能是异步的。它的功能纯粹是一个强制守卫,并非旨在延迟初始化。

    DisposableStackAsyncDisposableStack

    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 来警告用户未正确清理资源。

    const registry = new FinalizationRegistry(() => {
        console.warn("资源在没有被释放的情况下被垃圾回收。你忘记使用 'using' 了吗?");
    });
    
    class Resource {
        ...
    
        constructor() {
            registry.register(this, undefined, this);
            ...
        }
    
        [Symbol.dispose]() {
            registry.unregister(this);
            ...
        }
    }

    这里,分配 Resource 会向终结注册表添加条目,并使用自身作为注销令牌。当调用 [Symbol.dispose]() 方法时,Resource 将从终结注册表中移除。如果垃圾回收前未调用 [Symbol.dispose]() 方法,终结注册表的清理回调将触发向控制台发出警告。

    解决方法:Symbol.dispose Getter

    在此方法中,我们将 [Symbol.dispose] 定义为 getter 而不是方法。这使我们可以防止资源在无效状态下被使用,假设直接访问 [Symbol.dispose] 足以表明资源注册已发生。

    class Resource {
      #state = "未注册"; // 其中一个:"未注册"、"已注册"或"已释放"
    
      ...
    
      resourceOperation() {
        this.#throwIfInvalid();
        ...
      }
    
      get [Symbol.dispose]() {
        if (this.#state === "未注册") {
          this.#state = "已注册";
        }
        return Resource.#dispose;
      }
    
      static #dispose = function() {
        if (this.#state === "已释放") {
          return;
        }
        this.#state = "已释放";
        ...
      };
    
      #throwIfInvalid() {
        switch (this.#disposeState) {
        case "未注册":
          throw new TypeError("此资源必须通过 'using' 注册或附加到 'DisposableStack'");
        case "已释放":
          throw new ReferenceError("对象已释放");
        }
      }
    }

    这里,Resource 最初构造在"未注册"状态。对资源执行操作的尝试受到 #throwIfInvalid() 调用的保护,该调用检查资源是否已注册以及资源是否已释放。由于 [Symbol.dispose] 是 getter,访问 getter 会触发状态从"未注册"更改为"已注册",并返回实际释放方法,该方法将由 using 在包含块结束时调用。

    未提出的内容

    我们明确不提出具有 Python 的 上下文管理器 广度功能的特性。上下文管理器允许你拦截、替换甚至丢弃上下文内抛出的异常,这比所提议的机制复杂得多。有关完整异常处理的进一步讨论,请参阅 tc39/proposal-explicit-resource-management#49

    先前技术

    相关提案

    潜在语义

    CreateDisposableResource ( V, hint, method ) 将被修改如下:

      1. 如果 _method_ 不存在,则
        1. 如果 _V_ 是 *null* 或 *undefined*,则
          1. 将 _V_ 设为 *undefined*。
          1. 将 _method_ 设为 *undefined*。
        1. 否则,
          1. 如果 _V_ 不是对象,则抛出 *TypeError* 异常。
    +     1. 令 _enter_ 为 ? GetMethod(_V_, @@enter)。
    +     1. 如果 _enter_ 不是 *undefined*,则
    +         1. 将 _V_ 设为 ? Call(_enter_, _V_)。
    +         1. 如果 _V_ 不是对象,则抛出 *TypeError* 异常。
          1. 将 _method_ 设为 ? GetDisposeMethod(_V_, _hint_)。
          1. 如果 _method_ 是 *undefined*,则抛出 *TypeError* 异常。
      1. 否则,
        1. 如果 IsCallable(_method_) 是 *false*,则抛出 *TypeError* 异常。
      1. 返回 DisposableResource 记录 { [[ResourceValue]]: _V_, [[Hint]]: _hint_, [[DisposeMethod]]: _method_ }。

    API

    本提案将引入内置符号 @@enter,作为 Symbol 构造函数上名为 enter 的静态属性。

    待办事项

    以下是通过 TC39 提案流程 各阶段推进的高级任务列表:

    第 1 阶段入口标准

    • 确定了一位能推进该添加的"提案发起人"。
    • 概述问题或需求以及解决方案的大致形态的散文
    • 说明性示例的使用。
    • 高级API

    第 2 阶段入口标准

    第 3 阶段入口标准

    第 4 阶段入口标准

    • 已为主要使用场景编写 Test262 验收测试并 合并
    • 两个通过验收测试的兼容实现:[1][2]
    • 已将集成的规范文本的 拉取请求 发送到 tc39/ecma262。
    • ECMAScript 编辑器已签署 拉取请求