Deng Deng

为什么 Promise 没有 cancel?

Published on

很多人在使用 Promise 时都会产生一个直觉疑问:既然 Promise 表示一个异步任务,为什么不能取消它?

如果一个 fetch 请求还没完成,为什么不能直接:

const p = fetch(url)
p.cancel()

看起来非常合理。

但事实是——JavaScript 规范刻意没有给 Promise 添加 cancel。

这不是疏忽,而是设计选择。

理解这一点,会让你对 JavaScript 的抽象边界有更深层的认识。

一、Promise 到底是什么?

大多数人把 Promise 理解成: “一个异步任务”

但在规范层面,它其实是: 一个“未来的值”(future value)

Promise 代表的不是任务本身,而是:

这个操作最终会得到一个结果
- fulfilled
- rejected

它是一个结果的占位符。这点非常关键。

二、如果 Promise 有 cancel,会发生什么?

假设 Promise 支持:

p.cancel()

那么规范必须回答几个问题。

1️⃣ 状态模型怎么办?

Promise 目前只有三种状态:

pending
fulfilled
rejected

如果加入 cancel:

pending
fulfilled
rejected
cancelled ?

那 cancelled 是:

  • 一种新的状态?
  • 还是 rejected?
  • then 会不会执行?
  • catch 会不会执行?
  • finally 会不会执行?

这会直接破坏 Promise/A+ 的三态模型。

而 Promise 的核心设计之一就是: 状态只能单向变化 pending → fulfilled / rejected

加入 cancel 意味着引入新的分支语义,整个状态机复杂度暴涨。

2️⃣ 链式调用会崩溃

考虑:

const p = fetch(url)
const p2 = p.then(res => res.json())

如果我们:

p2.cancel()

问题来了:

  • 要不要取消 p?
  • 如果 p 被多个地方使用怎么办?
  • 谁拥有取消权?

Promise 是可多次消费的:

p.then(...)
p.then(...)

它不是“单一拥有者”的资源。

取消行为和这种“多订阅模型”天然冲突。

3️⃣ 控制权问题

再看:

function getData() {
return fetch(url)
}

调用方:

const p = getData()
p.cancel()

这意味着: 调用方可以强制终止内部逻辑

这会打破封装边界。

Promise 会从“结果表达”变成“可变控制对象”。

这违背了它的设计哲学。

三、Promise 是“值”,不是“任务”

这是最核心的一句话: Promise 表达的是值,不是任务。 任务属于“执行层”。

Promise 属于“结果层”。

取消属于“控制层”。

这三个层次不能混在一起。

如果 Promise 自带 cancel:

它就不再是一个不可变的 future value。

它会变成一个“可变的任务控制器”。

四、为什么 AbortSignal 更优雅?

后来,JavaScript 生态引入了:

AbortController
AbortSignal

这套设计非常聪明。

它没有修改 Promise。

它只是提供了: 一个外部的控制信号

比如:

const controller = new AbortController()

fetch(url, { signal: controller.signal })

controller.abort()

Promise 仍然只负责结果。

AbortSignal 负责取消控制。

这叫: 职责分离

五、AbortSignal 的优势

1️⃣ 不污染 Promise

Promise 仍然只有三态。

规范保持稳定。

2️⃣ 可传播

function request(signal) {
return fetch(url, { signal })
}

signal 可以一直往下传。

形成结构化取消

3️⃣ 可组合

const controller = new AbortController()

fetch(url, { signal: controller.signal })
sleep(1000, controller.signal)
otherTask(controller.signal)

controller.abort()

一个信号,可以广播取消多个任务。

这比每个 Promise 自带 cancel 优雅得多。

六、为什么当年规范拒绝 Promise.cancel?

历史上 TC39 确实讨论过:
  • Cancelable Promise
  • Promise.cancel
  • CancelToken

最终结论是: 取消不是 Promise 的职责。 如果把 cancel 放进 Promise:

  • 会破坏三态模型
  • 会破坏链式语义
  • 会导致控制权混乱
  • 会破坏 Promise/A+ 兼容性
  • 会让 Promise 从“值”变成“任务控制器”

于是规范选择了另一条路:

Promise 表达结果
AbortSignal 表达控制

这是一种抽象边界的选择。

七、从架构角度看

这种设计其实体现了几个核心思想:

1️⃣ 单一职责原则

Promise 只表达状态,不控制执行。

2️⃣ 不可变模型

Promise 一旦创建,状态单向流动。

3️⃣ 控制权外置

取消由外部信号控制,而不是内部方法。

4️⃣ 协作式取消

任务自己监听信号,而不是被强制终止。

八、一个类比

Promise 像:

一张已经生成的快递单号。

但你不能通过“单号”强制停止快递员取消快递。 而是通过:

快递系统的控制中心。

AbortSignal 就是这个控制中心。

结语

Promise 没有 cancel,并不是功能缺失。

而是:

一种抽象边界的自律。

它保持了 Promise 作为“未来值”的纯粹性。

取消,被提升为一个更高层的控制协议。

理解这一点,你会发现:

JavaScript 的设计,并不是随意拼接的 API。

它背后有一条非常清晰的抽象逻辑。