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/year/pending/proposal-thenable-curtailment.md.
  • 简体中文
  • Curtailing the power of "Thenables" S3

    中文标题:限制 “Thenables” 的能力(SafeResolve)

    提案概览
    提案速览

    该提案针对的问题是:then 查找会沿着整条原型链进行,包括内建原型和 Object.prototype,这使得意外的 thenable 分派成为可能,并为用户代码执行增加了额外路径。它引入名为 SafeResolve 的解析操作:如果解析 promise 不会运行用户代码,就直接尾调用 promise capability 的 \[\[Resolve\]\];如果可能运行用户代码,则入队一个新作业来解析该 promise,同时将其锁定,使后续解析被忽略。该操作面向 WebIDL promise 解析等内部消费者,而将其暴露给用户代码并非本提案的目标。

    Note

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

    限制 “Thenables” 的能力(SafeResolve)

    提案人:Matthew Gaudet(Mozilla)

    阶段:3(截至 2026 年 9 月全会)

    规范草案文本:https://tc39.es/proposal-thenable-curtailment/

    引言与问题:

    引用 MDN:

    早在 Promise 成为语言的一部分之前,JavaScript 生态就已经创建了多种 Promise 实现。尽管它们在内部表示方式上各不相同,但至少所有类 Promise 对象都实现了 Thenable 接口。thenable 实现了 .then() 方法,调用时会传入两个回调:一个在 promise 兑现时调用,一个在其被拒绝时调用。Promise 本身也是 thenable。

    为了与现有的 Promise 实现互操作,该语言允许用 thenable 代替 promise。例如,Promise.resolve 不仅能解析 promise,还会追踪 thenable。

    我们想要解决的问题是,then 查找会沿着整条原型链进行,包括内建原型和 Object.prototype。当处理那些本不应发生 “thenable” 分派的类型时,这一点尤其危险。

    为什么这是个问题?

    最具体的表现是安全漏洞。在所有标准工作中,以及整个 web 平台中,我们都必须时刻警惕这一为兼容性提供支持的特性。一旦疏忽,后果可能就是被利用:

    之所以认定这一问题会导致安全漏洞,是因为它增加了许多原本不存在的用户代码执行路径,而且这种可能性并不总是显而易见。

    尤其危险的情形是:规范作者把已知类型的新生对象视为已知量,却对它们调用 Promise.resolve。此时,如果这些对象被提供一个 JS wrapper,该 JS wrapper 的原型通常是 Object,从而使它们容易受到 thenable 的影响。

    除安全之外,这也只是徒增复杂性。WPT 中存在一些测试用例,其存在的唯一目的就是厘清当有人破坏 then 时的预期行为。

    我提议如何修复?

    我提议新增一个 “SafeResolve” 解析操作,它在检查是否存在运行用户代码的可能性之后解析 promise。如果不会运行任何用户代码,我们就直接尾调用到 promise capability 的 [[Resolve]] 操作。如果可能运行用户代码,我们则改为入队一个新作业,其职责是解析该 promise,同时像常规 resolve 函数那样锁定该 promise,使得后续任何解析都被忽略(非常感谢 Mark Miller 在 TG3 审查讨论中指出这一需求)。

    下一步是决定如何使用它。Mozilla DOM 有意探索用它来替代 WebIDL 中解析 promise 的步骤,并更广泛地为 Mozilla DOM 中所有 promise 解析代码提供支持。这将有助于让 C++ 代码更安全,因为它把 promise 解析变成了一项从不运行脚本的操作,从而简化了实现代码时所需的推理。

    关于 WebIDL 采纳的规范讨论正在 whatwg/webidl #1584 进行。

    将该能力暴露给用户代码不是本提案的目标,但最终可以作为后续提案来实现。

    这是无懈可击的修复吗?

    不是。其影响当然完全取决于采用的范围。

    在之前描述的安全漏洞中,这一缓解措施会修复

    但它不会修复

    • 规范中的 CVE-2024-43357。那需要在规范内部开始采用这一能力,属于规范性变更。

    兼容性

    当涉及 “thenable” 时,这可能改变微任务被解析的顺序。我们希望大多数处理 promise 的代码对执行顺序已经相对健壮。不过,这确实有可能引发 web 兼容性问题。

    实验:WebIDL

    问:我们能否用 SafePromiseResolve 替代 WebIDL 中的 promise 解析步骤?

    实验:在将 Firefox DOM 的 Promise resolve 步骤替换为 SafePromiseResolve1 的情况下运行 WPT。

    结果:

    绝大多数测试(如预期的那样)通过。

    超时失败:
    1. https://searchfox.org/firefox-main/source/testing/web-platform/tests/css/css-overflow/scroll-marker-in-display-none-column-crash.html -- 我没太弄明白这个。
    2. /custom-elements/when-defined-reentry-crash.html -- 这个用例在 Object.prototype 上使用 then 来实现不良目的。从某种意义上说,这正是我们试图解决的那类问题。https://issues.chromium.org/issues/40061097
    意外通过:
    1. /fetch/api/response/response-body-read-task-handling.html - 该测试使用 then 来窥探执行顺序。这个测试如今所测的已不再是它自以为在测的内容;不过,该测试 -也- 是为了解决这类 thenable 问题而创建的。
    测试失败
    1. /streams/readable-byte-streams/patched-global.any.js -- 显式使用 then 来窥探我们大概不希望被观察到的执行状态。
    2. /document-picture-in-picture/returns-window-with-document.https.html | requestWindow timing - assert\_equals: Got the expected order of actions expected "requestWindow,microtask,enter" but got "microtask,requestWindow,enter" -- 作业时序发生了变化,因为它用一个 window(WindowProxy)对象来解析 promise,这会多出一个 tick。
    3. /web-animations/interfaces/Animation/cancel.html;用 thenable 观察事件时序。

    先前技术与相关工作

    • Symbol.thenable “已撤回; 修改 Module Namespace 对象上的 thenable 特性不具备 web 兼容性,而且 允许非 Promise 方式使用 “then” 并不值得拖慢所有 Promise 操作”
    • Proposal Stabilize 正试图 为不变量提供可推广的机制 —— 这也可能成为我们 可以提供给用户代码的一项不变量。

    提案历史

    Footnotes

    1. 这比严格意义上的 WebIDL 范围略宽,因为我认为还存在非 IDL 方式使用 dom::Promise 的情况。 ↩