Promise到底能不能取消?怎么取消?你知道吗?

先说说Promise是个啥?

想象一下,Promise就像是你点外卖后收到的那张小票------它不代表你已经拿到饭了,但代表餐厅已经接单,并且承诺(Promise)会给你送餐。

这张小票有三种可能的结果:

  1. 🟡 等待中 - 饭还在做,骑手还没到
  2. 已完成 - 饭送到了,可以开吃了
  3. 已拒绝 - 餐厅打电话说卖完了,做不了

那为啥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超时 设置最大等待时间 实现简单 不是真正的取消
  1. 大部分时候,你其实不需要取消Promise,让它自然完成或失败就好
  2. 需要取消网络请求时,优先使用AbortController
  3. 特殊需求时,再考虑自己实现取消逻辑
相关推荐
程序员爱钓鱼7 分钟前
Rust impl详解:为Struct定义方法与关联函数
前端·后端·rust
用户0595401744617 分钟前
Qdrant 召回不一致踩坑实录:跑了 300 次测试才发现是索引没刷新
前端·css
Highcharts.js28 分钟前
Highcharts 主流前端框架无缝集成指南
前端·vue.js·前端框架·highcharts·可视化图表
JAVA面经实录91730 分钟前
网络编程基础(Java Web/分布式前置·完整版)(十一)
java·前端·网络
IT_陈寒32 分钟前
搞不定JavaScript的数组去重?你可能漏了这两个坑
前端·人工智能·后端
三84441 分钟前
CSRF跨站请求伪造基础
前端·csrf
岁岁种桃花儿1 小时前
Vue核心语法第十一篇:绑定样式
前端·javascript·vue.js
程序员黑豆9 小时前
Java类型推断完全指南:从var到菱形运算符,掌握使用限制与最佳实践
java·前端·ai编程
To_OC9 小时前
踩了个 TS 的坑之后,我终于把 type 和 interface 掰明白了
前端·react.js·typescript