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/proposal-mass-proxy-revocation.md.
  • 简体中文
  • Mass Proxy Revocation S1

    中文标题:批量代理撤销

    提案概览
    提案速览

    该提案解决了膜中一次性撤销大量代理的挑战,目前需要为每个代理显式创建 revoker 函数。它向 Proxy 和 Proxy.revocable 引入第三个参数以传递“信号”符号,并提供 Proxy.createSignal 和 Proxy.finalizeSignal 来管理撤销组。

    Note

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

    批量代理撤销

    状态

    • 冠军:Mark S. Miller, Jack Works(所有功劳归功于 Alex 和 Bradley)
    • 作者:Alexander J. Vincent
    • 荣誉作者:Bradley Farias
    • 阶段:1

    动机

    膜(Membrane),使用 Proxy 和 WeakMap 将一个对象图(可以认为是“Realm”)与其他对象图分离,每个对象图中可能有成百上千个代理。在撤销一个对象图时,膜必须一次性撤销所有这些代理,以及在其他对象图中指向该对象图内对象的代理。

    我们建议创建和跟踪“撤销信号”符号(symbols),开发者可以通过第三个参数将其实例传递给 new Proxy()Proxy.revocable()。用户可以通过 Proxy.createSignal() 静态函数创建这些信号,并通过 Proxy.finalizeSignal(signal) 静态函数撤销它们。(我们知道取消(Cancellation)提案,并且非常愿意用该提案的 API 替换此机制。)

    在 TypeScript 中(从 Microsoft 的 TypeScript 项目复制,遵循 Apache 许可证),这将如下所示:

    interface ProxyConstructorOptions {
      signal?: symbol;
    }
    
    interface ProxyConstructor {
      revocable<
        T extends object
      >(
        target: T,
        handler: ProxyHandler<T>,
        options?: ProxyConstructorOptions
      ): {
        proxy: T;
        revoke: () => void;
      };
    
      new <
        T extends object
      >(
        target: T,
        handler: ProxyHandler<T>,
        options?: ProxyConstructorOptions
      ): T;
    
      createSignal() : symbol;
    
      finalizeSignal(signal: symbol): void;
    }
    
    declare var Proxy: ProxyConstructor;

    示例:

    const s1 = Proxy.createSignal();
    
    const b1 = new Proxy([], {}, { signal: s1 });
    const {proxy: b2, revoke: b2_revoke} = Proxy.revocable({}, {}, { signal: s1 });
    
    Proxy.finalizeSignal(s1); // 杀死 s1, b1, b2。

    使用场景

    膜撤销整个对象图

    考虑下面的情景,其中每个平面是一个 Realm,原始对象是球体,代理是半球体,垂直圆柱表示代理与其对象之间的连接。(这是膜的几何模型。)

    图 1

    三个 Realm 的可视化

    如果我们想要撤销对黄色 Realm 的访问,我们不能仅撤销黄色 Realm 中的代理。蓝色和绿色 Realm 中的“onload”代理只有在尝试访问黄色“onload”对象并抛出异常时才会发现。它们已经死了但不知道。客户端代码负责处理这些异常。

    // 这是伪代码!
    const membrane = new Membrane;
    
    const green = new ShadowRealm;
    const yellow = new ShadowRealm;
    
    yellow.car = new Car;
    
    // 在绿色 Realm 中设置对 yellow.car 的初始引用:
    membrane.bindProxyForObject(yellow, green, "car");
    
    // 在膜内部,运行以下代码:
    {
      const yellowGreenSignal = Proxy.createSignal();
      this.#cacheRevokeSignal(yellowGreenSignal, [yellow, green]);
    
      const shadowTarget = {};
      const proxy = new Proxy(shadowTarget, membraneProxyHandler, { signal: yellowGreenSignal });
    
      green.car = proxy;
    
      this.#bindOneToOne(yellow, yellow.car, green, proxy);
    }
    
    // 在绿色 Realm 中,运行以下代码。
    green.car.driver = new Driver;
    
    // 在膜内部,运行以下代码:
    {
      const yellowGreenSignal = this.#getRevokeSignal(yellow, green); // 从之前获取 yellowGreenSignal
    
      const shadowTarget = {};
      const proxy = new Proxy(shadowTarget, membraneProxyHandler, { signal: yellowGreenSignal });
      yellow.car.driver = proxy;
    
      this.#bindOneToOne(green, green.car.driver, yellow, proxy);
    }
    
    membrane.revoke(yellow);
    
    // 在膜内部,运行以下代码:
    {
      const signals = this.#getRevokingSignals(yellow); // 返回 [yellowGreenSignal]
      signals.forEach(s => Proxy.finalizeSymbol(s));
      // 这触发 yellowGreenSignal,从而击倒 green.car 和 yellow.car.driver
    }

    注意,这里我们不指定膜的 API。膜的方法仅用于说明。

    这样做的一个特别好处是将撤销管理(以及代理的垃圾回收)交还给 JavaScript 引擎。显式生成 revoker 函数的需求变得多余,但为了向后兼容,我们会保留它。

    内存优化

    如果我们不为每个代理实际创建 revoker 函数,这意味着更少的内存分配和更少的垃圾回收压力。

    描述

    面向开发者的功能使用文档

    比较

    对各种相关编程语言和/或库的比较。如果这是第一个做这种事情的语言或库,请解释原因。如果这是一个标准库特性,与 JavaScript 生态系统的比较会很好;如果这是一个语法特性,那可能不实际,比较可能仅限于其他编程语言。

    有关于跨 Realm 传递 AbortSignals 的先例。参见 issue #1

    实现

    Polyfill/transpiler 实现

    该提案的 JavaScript 实现,理想情况下以方便、真实实验的方式打包。参见 implement.md 了解创建有用原型实现的细节。

    原生实现

    问答

    常见问题,或你认为可能被问到的问题。问题跟踪器上的 issue 或过去审查中的问题可能成为这些问题的来源。

    :为什么这需要内置,而不是用 JavaScript 实现?

    :(1)我们希望将代理的实际管理交还给 JavaScript 引擎。膜存储数百个 revoker 然后同步撤销它们是痛苦的。(2)我们认为这种设计对 compartment 更有效。

    :为什么要有第三个参数?

    :我们认为这是保持向后兼容并添加我们请求的功能的最简单方法。我们也意识到正确塑造这第三个参数 API 的重要性。

    :这与取消(Cancellation)提案和/或 AbortController有什么关系?(#13

    :我们当然愿意调整我们的提案,使之与取消提案兼容和/或依赖它。关于 AbortController,SES 认为让 TC39 提案依赖 Web API 是不可行的,除非 AbortController 成为 ECMAScript 的一部分(极不可能)。

    特别是,Proxy.createSignal()Proxy.finalizeSignal(signal) 方法我们可以用 TC39 的其他标准 API 替换。

    超出范围

    观察撤销

    本提案旨在允许创建可撤销的代理分组。一般的信号机制一直是 TC39 讨论的主题,我们认为本提案无需观察机制即可达到目的。没有明确理由说明为什么不能在此处提出的 API 中添加信号作为后续功能。添加信号会带来各种问题,如重入性和与其他宿主环境特性的兼容性。