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-cancellation.md.
  • 简体中文
  • Cancellation API S1

    中文标题:取消 API

    提案概览
    提案速览

    该提案将 WHATWG AbortController 和 AbortSignal 接口中一个尚待确定的子集添加到 JavaScript 中,仅在绝对必要时进行小幅修改。其目标是完成必要的规范工作,使包括 JavaScript 规范本身在内的各种规范能够使用 AbortController,而无需依赖 EventTarget 等随之而来的其余机制。预计这不会影响已经提供 AbortController 的平台上的开发者,只是可能需要进行一些扩展,而这些扩展也将包含在这些平台中。

    Note

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

    ECMAScript 取消机制

    本提案旨在将 WHATWG AbortController 和 AbortSignal 接口中一个尚待确定的子集添加到 JavaScript 中,并在绝对必要时进行小幅修改。

    历史上,本提案考虑过其他方案,你可以在 stage 0 README 和 git 历史中看到这些方案。此后,AbortController 已经发布,并在各种 JavaScript 运行时中变得无处不在,委员会认为标准化一种不同的取消机制并不合理。因此,现在的目标是完成必要的规范工作,使各种规范(包括 JavaScript 规范本身)能够使用 AbortController,而无需依赖随之而来的其余机制(例如 EventTarget)。

    预计这不会影响已经提供 AbortController 的平台上的开发者,只是可能需要进行一些扩展,而这些扩展将包含在这些平台中。

    状态

    阶段: 1
    负责人: Ron Buckton (@rbuckton)、James M. Snell (@jasnell)、Kevin Gibbons (@bakkot)

    更多信息请参阅 TC39 提案流程。

    作者

    动机

    • 一种清晰且一致的取消异步操作的方法
    • 一种具有许多用例的通用协调机制:
      • 同步观察(例如在游戏循环中)
      • 异步观察(例如网络请求)
      • 易于在 async 函数中使用
    • 一个可在多种宿主环境(浏览器、NodeJS、IoT/嵌入式等)中复用的通用 API。

    先例

    架构

    以下是 Dean Tribble 在 es-discuss 邮件列表 上提供的一些架构观察:

    取消请求,而不是结果
    Promise 就像异步场景中的对象引用;任何特定的 Promise 都可能被返回或传递给多个客户端。通常,如果返回或传入的引用被另一个客户端从他们脚下直接抽走,程序员会感到意外。当考虑一个接收传入 Promise 的库时,这一点尤其明显。对 Promise 使用“cancel”就像对对象引用提供 delete;使用它是危险的,而且依赖别人使用它也不可靠。

    取消是异构的
    考虑取消单个活动可能会产生误导。在大多数系统中,当取消发生时,许多互不相关的任务可能因同一原因而需要被取消。例如,如果用户在看到大型增量查询的前几个结果后按下停止按钮,应该发生什么?

    • 对更多查询结果的异步获取应被终止,连接应关闭
    • 将远程结果处理为可渲染形式的后台计算应停止
    • 尚未渲染的内容的渲染应停止。这可能包括获取不再感兴趣的条目的次要内容(例如,通过复杂内容搜索找到的歌曲的专辑封面)
    • “加载更多”的动画应停止,并应替换为“用户已取消”
    • 等等。

    其中一些处于不同的抽象层次,对于任何非平凡应用,都不存在某一段代码能够知道如何终止所有这些活动。这类系统还要求取消支持在许多非常不同类型的组件之间保持一致。但如果每个活动都接收一个 cancellationToken,在上述示例中,它们只会收到那个在用户按下停止时会被取消的令牌,然后正确的事情就会发生。

    取消应该是智能的
    库可以而且应该对如何取消保持智能。在异步查询的情况下,一旦来自服务器的查询结果返回,完成解析并将其缓存,而不是条件反射般地直接丢弃它,可能是合理的。例如,在经纪系统中,往返服务器获取最新数据是昂贵的部分。一旦这个过程已经启动并且结果正在返回,将其放入本地缓存以便用户再次请求时可用是高效的。如果应用程序启动了另一个 worker,让该 worker 完成(以便你可以复用它)可能比突然终止它(需要丢弃正在运行的 worker 和缓存状态)更高效。

    取消是一场竞态
    在异步系统中,新的活动可能会由那些本身已被调度但当前并未运行的请求持续调度。取消动作需要在这个环境中运行。当取消开始时,你可以把它看作一个信号冲出去,追赶所有为完成现已取消的目标而启动的计算。其中一些计算可能会选择完成(见上面的缓存示例)。一些计算可能会在该工作本身收到信号之前继续启动更多工作(是的,这是一个 bug,但人们会写出有 bug 的代码)。在异步系统中,取消并非即时发生。因此,询问“取消是否已经完成?”是不可行的,因为那不是一个定义良好的状态。确实,可能存在被调度且应当、也确实不会被取消的代码(例如,发布/订阅系统的结果处理器),但该代码会调度那些将被取消的工作(解析对现已取消查询的更新发布)。

    取消是“不关心”
    因为智能取消有时不会停止任何事情,而且在异步环境中,取消与进展处于竞态之中,所以它充其量只是“尽力而为”。当一组计算被取消时,取消这些活动的一方是在说“我不再关心这是否完成”。这与说“我想阻止它完成”有重要区别。前者是一种可广泛使用的资源减少手段。后者只有在围绕原子性和事务进行了昂贵工程的系统中才能有效实现。当取消是“不关心”时,取消逻辑会变得简单得多,这令人惊叹。

    取消需要关注点分离
    在多个事物被取消的模式中,取消的来源很少是要被取消的事物之一。如果一个为可取消活动(加载这张图片)而调用的库,仅仅因为它们关心同一个取消事件就取消了一个无关的服务器查询,那会令人意外。我觉得有趣的是,取消令牌与取消来源之间的分离,映射了 Promise 与其 resolver 之间的分离。

    取消恢复是短暂的
    随着任务推进,清理动作可能会改变。在上面的示例中,如果数据表在滚动时请求更多结果,那么当存在一个尚未完成的、用于获取更多数据的查询时,其取消行为很可能与它已经拥有当前页面所需显示的全部内容时大不相同。这就是为什么“register”方法会返回一个用于注销该动作的能力。