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. 特殊需求时,再考虑自己实现取消逻辑
相关推荐
前端W1 分钟前
腾讯地图组件使用说明文档
前端
页面魔术3 分钟前
无虚拟dom怎么又流行起来了?
前端·javascript·vue.js
胡gh4 分钟前
如何聊懒加载,只说个懒可不行
前端·react.js·面试
Double__King7 分钟前
巧用 CSS 伪元素,让背景图自适应保持比例
前端
Mapmost8 分钟前
【BIM+GIS】BIM数据格式解析&与数字孪生适配的关键挑战
前端·vue.js·three.js
一涯9 分钟前
写一个Chrome插件
前端·chrome
鹧鸪yy16 分钟前
认识Node.js及其与 Nginx 前端项目区别
前端·nginx·node.js
跟橙姐学代码17 分钟前
学Python必须迈过的一道坎:类和对象到底是什么鬼?
前端·python
汪子熙19 分钟前
浏览器里出现 .angular/cache/19.2.6/abap_test/vite/deps 路径究竟说明了什么
前端·javascript·面试