先说说Promise是个啥?
想象一下,Promise就像是你点外卖后收到的那张小票------它不代表你已经拿到饭了,但代表餐厅已经接单,并且承诺(Promise)会给你送餐。
这张小票有三种可能的结果:
- 🟡 等待中 - 饭还在做,骑手还没到
- ✅ 已完成 - 饭送到了,可以开吃了
- ❌ 已拒绝 - 餐厅打电话说卖完了,做不了
那为啥Promise不能取消呢?
原因1:设计初衷不同
Promise的设计目标特别简单:只管最终结果(成功或失败),不管中间过程。
就像你点了外卖后:
- 你只关心最后能不能吃到饭
- 你不关心厨师怎么做饭、骑手怎么送餐
- 取消订单是餐厅和平台的事,不是小票的功能
原因2:取消太复杂了
如果Promise能取消,会出现很多麻烦事:
javascript
// 假如Promise能取消,会出现这种混乱情况:
const order = 点外卖();
order.then(吃饭); // 饭到了就吃
order.cancel(); // 突然取消
// 问题来了:
// 1. 如果饭已经在路上了怎么办?
// 2. 骑手接到取消通知需要时间,这期间饭送到了算谁的?
// 3. 钱怎么退?谁去跟餐厅沟通?
原因3:状态机不能乱改
Promise的状态变化特别简单:
lua
成功
🟡 ------> ✅
失败
🟡 ------> ❌
一旦变成✅或❌,就再也不能变了。
如果加入取消功能,就变成了:
rust
成功 取消
🟡 ------> ✅ 🟡 ------> 🚫
失败
🟡 ------> ❌
这样复杂度直接翻倍,而且会产生各种边界情况
需要处理。
那我真的需要取消功能怎么办?
虽然原生Promise不能取消,但我们有替代方案!
方案1:用AbortController取消网络请求(最常用)
javascript
// 创建一个控制器
const controller = new AbortController();
// 发起请求时带上"取消开关"
fetch('/api/data', {
signal: controller.signal // 把这个开关交给fetch
})
.then(response => response.json())
.then(data => console.log('收到数据:', data))
.catch(err => {
if (err.name === 'AbortError') {
console.log('请求被取消了!');
} else {
console.log('其他错误:', err);
}
});
// 需要取消的时候
document.getElementById('cancel-btn').addEventListener('click', () => {
controller.abort(); // 按下取消按钮!
});
方案2:自己写个可取消的Promise
javascript
// 定义一个可取消的异步任务
function createCancellableTask(taskFn) {
let isCancelled = false; // 标记任务是否被取消
let cancelCallbacks = []; // 存放取消时需要执行的清理函数
// 用 Promise 封装任务
const promise = new Promise((resolve, reject) => {
taskFn(
(result) => {
// 如果任务已取消,则返回一个特殊的错误对象
if (isCancelled) {
reject({ cancelled: true });
} else {
resolve(result); // 否则正常完成
}
},
(error) => {
// 如果任务已取消,同样返回特殊错误
if (isCancelled) {
reject({ cancelled: true });
} else {
reject(error); // 否则抛出原始错误
}
}
);
});
return {
promise, // 暴露 promise,方便调用 then/catch
cancel() {
// 设置取消标志
isCancelled = true;
// 执行所有注册的清理函数
cancelCallbacks.forEach(cb => cb());
},
onCancel(cb) {
// 注册一个取消时的回调
cancelCallbacks.push(cb);
}
};
}
// 使用示例
const { promise, cancel, onCancel } = createCancellableTask((resolve, reject) => {
// 模拟一个异步任务:3 秒后完成
const timer = setTimeout(() => resolve('Task finished!'), 3000);
// 注册取消时的清理逻辑
onCancel(() => {
clearTimeout(timer);
console.log('Timer cleared');
});
});
// 监听任务的完成或失败
promise
.then(result => console.log(result))
.catch(error => {
if (error.cancelled) {
console.log('Task was cancelled'); // 捕获到取消
} else {
console.error('Task failed:', error); // 捕获到一般错误
}
});
// 2 秒后主动取消任务
setTimeout(cancel, 2000);
方案3:用Promise.race实现超时自动取消
javascript
function 带超时的请求(原始Promise, 超时时间) {
const 超时Promise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('请求超时了!')), 超时时间);
});
return Promise.race([原始Promise, 超时Promise]);
}
// 使用:5秒内没响应就自动取消
带超时的请求(fetch('/api/slow-data'), 5000)
.then(响应 => 响应.json())
.then(数据 => console.log(数据))
.catch(错误 => console.log(错误.message));
总结一下
方案 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
AbortController | 取消fetch请求 | 原生支持,最简单 | 只能用于fetch |
自定义包装器 | 任何异步操作 | 最灵活,万能方案 | 需要自己实现 |
Promise.race超时 | 设置最大等待时间 | 实现简单 | 不是真正的取消 |
- 大部分时候,你其实不需要取消Promise,让它自然完成或失败就好
- 需要取消网络请求时,优先使用AbortController
- 特殊需求时,再考虑自己实现取消逻辑