网站首页 > 技术文章 正文
在现代前端开发中,Promise 和 async/await 已经成为处理异步操作的基石。然而,一个常见的棘手问题是:如何取消一个已经开始的 Promise?
比如,用户发起一个数据请求,但在请求完成前又导航到了其他页面;或者用户在一个搜索框中快速输入,我们需要取消前一次的搜索请求,只保留最后一次。在这些场景下,取消一个进行中的 Promise 就显得至关重要。
核心问题:为什么 Promise 本身不可取消?
首先,我们需要理解 Promise 的核心设计理念。一个 Promise 代表一个异步操作的最终结果。它的状态一旦从 pending(进行中)变为 fulfilled(已成功)或 rejected(已失败),就永远不会再改变。
Promise 本身不提供取消机制,原因如下:
- 状态不可逆:这是 Promise 的核心规范。一旦状态改变,就形成了一个确定的、不可变的结果。
- 单一责任:Promise 的职责是传递价值和状态,而不是控制异步操作本身的执行流程。发起异步操作的函数(如 fetch)才是执行者。
打个比方:你寄出了一封信(发起了一个 Promise),你不能在信件投递过程中把它神奇地从邮政系统里撤回来。你能做的,是在信件送达时(Promise 完成时),选择忽略它。
我们的目标,就是实现这种“忽略”机制,并尽可能地通知底层的异步操作停止工作,以节省资源。
AbortController
AbortController 是目前实现 Promise 取消的最佳实践和标准方案。它最初是为取消 fetch 请求而设计的,但其通用性使其可以与任何异步操作集成。
AbortController 的工作方式:
- 创建一个 AbortController 实例。
- controller.signal:这是一个 AbortSignal 对象,可以传递给需要支持取消的异步函数(如 fetch)。
- controller.abort():调用此方法来发出“中止”信号。
- 当 abort() 被调用时,signal 会通知所有监听它的异步操作。对于 fetch 来说,它会自动中止网络请求并让 Promise reject 一个名为 AbortError 的错误。
1. 与 fetch配合使用
这是 AbortController 最常见的用法。
// 创建一个控制器
const controller = new AbortController();
const signal = controller.signal;
console.log("开始请求数据...");
fetch('https://api.example.com/data', { signal })
.then(response => response.json())
.then(data => {
console.log("数据获取成功:", data);
})
.catch(err => {
// 检查错误是否是由于中止操作引起的
if (err.name === 'AbortError') {
console.log('Fetch 请求已被用户中止。');
} else {
console.error('Fetch 错误:', err);
}
});
// 比如,在 500 毫秒后用户决定取消请求
setTimeout(() => {
console.log("正在中止请求...");
controller.abort();
}, 500);
在 async/await 语法中同样清晰:
async function fetchData() {
const controller = new AbortController();
const signal = controller.signal;
// 如果 3 秒内用户点击了取消按钮,就中止请求
const cancelButton = document.getElementById('cancel-btn');
cancelButton.onclick = () => controller.abort();
try {
const response = await fetch('...', { signal });
const data = await response.json();
console.log(data);
} catch (err) {
if (err.name === 'AbortError') {
console.log('请求已取消');
} else {
console.error('请求失败', err);
}
}
}
2. 在自定义 Promise 中使用 AbortController
你也可以让你自己的异步函数支持 AbortSignal。
原理:
- 你的函数需要接收 signal 作为参数。
- 在异步操作的关键节点,检查 signal.aborted 属性。如果为 true,则提前退出。
- 使用 signal.addEventListener('abort', ...) 来注册清理逻辑(如清除定时器)。
function longRunningTask(signal) {
return new Promise((resolve, reject) => {
// 如果信号在任务开始前就已经被中止,则直接拒绝
if (signal.aborted) {
return reject(new DOMException('任务已中止', 'AbortError'));
}
console.log("耗时任务开始...");
const timeoutId = setTimeout(() => {
console.log("耗时任务完成!");
resolve("成功!");
}, 5000);
// 监听 abort 事件,用于清理工作
signal.addEventListener('abort', () => {
clearTimeout(timeoutId); // 清除定时器,真正中止了操作
console.log("定时器已被清除,任务已中止。");
reject(new DOMException('任务已中止', 'AbortError'));
});
});
}
// --- 使用示例 ---
const controller = new AbortController();
longRunningTask(controller.signal)
.then(console.log)
.catch(err => {
if (err.name === 'AbortError') {
console.log('捕获到 AbortError,任务被成功取消。');
}
});
// 2秒后取消任务
setTimeout(() => {
controller.abort();
}, 2000);
优点:
- 官方标准:是 W3C 和 WHATWG 定义的标准 API。
- 真正中止底层操作:fetch 会中止网络连接,自定义函数也可以通过它来清理资源(如清除定时器),避免了不必要的浪费。
- 语义清晰:通过专门的 AbortError 来区分“取消”和“其他错误”,代码更健壮。
- 组合性强:一个 AbortSignal 可以传递给多个 Promise,实现批量取消。
虽然 Promise 本身的核心设计使其不可变,但通过 AbortController 这一强大的模式,我们已经可以非常有效地控制和终止异步流程,编写出更健壮、更高效的应用程序。
猜你喜欢
- 2025-06-24 服务端性能测试实战3-性能测试脚本开发
- 2025-06-24 跨标签页通信(六):Cookie(cookie跨页面)
- 2025-06-24 崩溃!代码总掉链子?6 个 JavaScript 技巧助你稳操胜券
- 2025-06-24 Web页面如此耗电!到了某种程度,会是大损失
- 2025-06-24 setTimeout 出大 bug?为什么需要 setBigTimeout?
- 2025-06-24 悠然!午休十分钟分清 Vue 的 watch 和 computed,面试不发怵
- 2025-06-24 科普:CPU空闲时在忙什么?(cpu空闲温度)
- 2025-06-24 html5大神结合js带你研究古老读心术,你的心思早被猜透
- 2025-06-24 JWT + Refresh + SSO模版(jwt demo)
- 2025-06-24 西门子PLCS7-1200 从0到1(西门子plcs7-1200介绍)
你 发表评论:
欢迎- 06-24发现一款开源宝藏级工作流低代码快速开发平台
- 06-24程序员危险了,这是一个 无代码平台+AI+code做项目的案例
- 06-24一款全新的工作流,低代码快速开发平台
- 06-24如何用好AI,改造自己的设计工作流?
- 06-24濮阳网站开发(濮阳网站建设)
- 06-24AI 如何重塑前端开发,我们该如何适应
- 06-24应届生靠这个Java简历模板拿下了5个offer
- 06-24服务端性能测试实战3-性能测试脚本开发
- 561℃Oracle分析函数之Lag和Lead()使用
- 557℃几个Oracle空值处理函数 oracle处理null值的函数
- 547℃Oracle数据库的单、多行函数 oracle执行多个sql语句
- 543℃0497-如何将Kerberos的CDH6.1从Oracle JDK 1.8迁移至OpenJDK 1.8
- 540℃Oracle 12c PDB迁移(一) oracle迁移到oceanbase
- 534℃【数据统计分析】详解Oracle分组函数之CUBE
- 520℃最佳实践 | 提效 47 倍,制造业生产 Oracle 迁移替换
- 512℃Oracle有哪些常见的函数? oracle中常用的函数
- 最近发表
- 标签列表
-
- 前端设计模式 (75)
- 前端性能优化 (51)
- 前端模板 (66)
- 前端跨域 (52)
- 前端缓存 (63)
- 前端react (48)
- 前端aes加密 (58)
- 前端脚手架 (56)
- 前端md5加密 (54)
- 前端富文本编辑器 (47)
- 前端路由 (61)
- 前端数组 (73)
- 前端js面试题 (50)
- 前端定时器 (59)
- Oracle RAC (73)
- oracle恢复 (76)
- oracle 删除表 (48)
- oracle 用户名 (74)
- oracle 工具 (55)
- oracle 内存 (50)
- oracle 导出表 (57)
- oracle 中文 (51)
- oracle的函数 (57)
- 前端调试 (52)
- 前端登录页面 (48)
本文暂时没有评论,来添加一个吧(●'◡'●)