JavaScript async/await 本身不支持取消令牌,但可通过 AbortController 与 Promise.race 等组合模拟取消语义,核心是监听 signal.aborted 并主动 reject AbortError,实现协作式中断。

JavaScript 原生的 async/await 本身不支持取消令牌(cancellation token),但可以通过组合 AbortController 和 Promise 封装来模拟类似 .NET 或 Rust 中的取消语义。核心思路是:在异步操作中监听 signal.aborted,并在被取消时主动 reject 一个特定错误(如 AbortError),让调用方能捕获并响应取消。
使用 AbortController 配合 Promise.race 实现基础取消
这是最轻量、兼容性好(现代浏览器和 Node.js ≥15.4)的方式。适用于封装 fetch、定时器、或自定义异步逻辑:
function sleep(ms, signal) {
return Promise.race([
new Promise(resolve => setTimeout(resolve, ms)),
new Promise((_, reject) => {
if (signal?.aborted) {
reject(new DOMException('Aborted', 'AbortError'));
return;
}
signal.addEventListener('abort', () => {
reject(new DOMException('Aborted', 'AbortError'));
}, { once: true });
})
]);
}
<p>// 使用示例
async function example() {
const controller = new AbortController();
setTimeout(() => controller.abort(), 1000);</p><p>try {
await sleep(5000, controller.signal);
console.log('done');
} catch (err) {
if (err.name === 'AbortError') {
console.log('被取消了');
} else {
throw err;
}
}
}
</p>
封装通用可取消的 async 函数工具
把取消逻辑抽成高阶函数,让任意 async 函数支持 signal:
function cancellable(fn) {
return async function(...args) {
const signal = args.at(-1)?.signal;
if (!signal || typeof signal !== 'object' || !('aborted' in signal)) {
return fn(...args);
}
<pre class="brush:php;toolbar:false;">const abortPromise = new Promise((_, reject) => {
if (signal.aborted) {
reject(new DOMException('Aborted', 'AbortError'));
return;
}
signal.addEventListener('abort', () => {
reject(new DOMException('Aborted', 'AbortError'));
}, { once: true });
});
return Promise.race([
fn(...args.filter((_, i) => i !== args.length - 1)),
abortPromise
]);}; }
// 使用 const fetchWithCancel = cancellable(async (url) => { const res = await fetch(url); return res.json(); });
// 调用时传入 signal const controller = new AbortController(); fetchWithCancel('/api/data', { signal: controller.signal }) .catch(err => { if (err.name === 'AbortError') console.log('请求已取消'); });
注意与原生 fetch 取消的差异
原生 fetch() 已内置对 AbortSignal 的支持,无需手动封装:
-
直接传 signal 即可中断请求:fetch 会在底层终止网络连接,并立即 reject 带
AbortError的 Promise。 - 不要重复封装 fetch:避免多层 race 导致行为不可控或错误类型混乱。
-
signal 是只读的:不能手动触发
abort()以外的事件,也不应修改其属性。
进阶:类 CancellationToken 的轻量实现(非标准但更直观)
如果你习惯 C# 风格的 cancellationToken.throwIfCancellationRequested(),可以这样写:
class CancellationToken {
constructor(signal) {
this.signal = signal;
}
throwIfCancellationRequested() {
if (this.signal?.aborted) {
throw new DOMException('Operation cancelled', 'AbortError');
}
}
get isCancellationRequested() {
return this.signal?.aborted === true;
}
}
<p>// 在 async 函数中定期检查
async function longRunningTask(token) {
for (let i = 0; i setTimeout(r, 200));
}
return 'completed';
}
</p>
不复杂但容易忽略:取消不是“强制终止”执行,而是协作式中断——你需要在关键节点主动检查 signal 状态,或依赖已支持 signal 的原生 API(如 fetch、setTimeout with AbortSignal)。真正可靠的取消,靠的是设计时就预留退出点,而不是运行时强行杀掉 Promise。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











