JavaScript 中页面关闭如何取消请求

93 阅读5分钟

在 JavaScript 中,取消请求的方案取决于你使用的请求方式(原生 XMLHttpRequest/fetch API,或第三方库如 axios),核心思路是调用请求对象的「取消接口」或利用「信号机制」中断请求。以下是不同场景的具体实现的方案:

一、原生 XMLHttpRequest(XHR):abort () 方法

XMLHttpRequest 是传统的请求方式,内置 abort() 方法可直接取消未完成的请求,兼容性最好(支持所有浏览器)。

实现步骤:

  1. 创建 XMLHttpRequest 实例;
  2. 调用 open() 初始化请求后,可通过 abort() 取消;
  3. 取消后会触发 abort 事件,可在事件回调中处理取消逻辑(如清理状态)。

示例代码:

// 1. 创建 XHR 实例
const xhr = new XMLHttpRequest();

// 2. 监听 abort 事件(请求被取消时触发)
xhr.addEventListener('abort', () => {
  console.log('XHR 请求已取消');
});

// 3. 监听错误事件
xhr.addEventListener('error', (err) => {
  console.log('XHR 请求失败:', err);
});

// 4. 初始化并发送请求
xhr.open('GET', 'https://api.example.com/data', true);
xhr.send();

// 5. 取消请求(如 1 秒后取消,或用户触发取消按钮)
setTimeout(() => {
  xhr.abort(); // 调用 abort() 取消请求
}, 1000);

关键说明:

  • 已完成的请求(readyState === 4)调用 abort() 无效;
  • 取消后 xhr.status 会变为 0,可通过该状态判断请求是否被取消。

二、原生 fetch API:AbortController 信号

fetch API 本身没有 abort() 方法,需通过 AbortController(ES2017 标准)的信号机制取消请求,现代浏览器(Chrome 66+、Firefox 60+、Safari 12.1+)均支持。

实现步骤:

  1. 创建 AbortController 实例,其 signal 属性作为信号传递给 fetch
  2. fetch 请求时,通过 signal 监听取消信号;
  3. 调用 controller.abort() 触发取消,fetch 会抛出 AbortError 异常,需通过 catch 捕获。

示例代码:

// 1. 创建 AbortController 实例
const controller = new AbortController();
const signal = controller.signal; // 取消信号

// 2. 发送 fetch 请求(传入 signal)
fetch('https://api.example.com/data', { signal })
  .then((response) => {
    if (!response.ok) throw new Error('请求失败');
    return response.json();
  })
  .then((data) => console.log('请求成功:', data))
  .catch((err) => {
    // 3. 捕获取消异常(区分取消和其他错误)
    if (err.name === 'AbortError') {
      console.log('fetch 请求已取消');
    } else {
      console.log('fetch 请求失败:', err);
    }
  });

// 4. 取消请求(如用户点击取消按钮)
setTimeout(() => {
  controller.abort(); // 触发取消信号
}, 1000);

关键说明:

  • 同一 signal 可关联多个 fetch 请求,调用 abort() 会取消所有关联请求;
  • 必须通过 err.name === 'AbortError' 区分 “取消” 和 “网络错误”“请求失败” 等场景。

三、第三方库:axios(CancelToken 或 AbortController)

axios 是前端常用的请求库,提供了两种取消方案:旧版 CancelToken(已废弃)  和 新版 AbortController(推荐,对齐原生 fetch)

方案 1:新版 AbortController(推荐,v0.22.0+ 支持)

与原生 fetch 用法一致,axios 支持直接传入 signal 参数:

import axios from 'axios';

// 1. 创建 AbortController 实例
const controller = new AbortController();
const signal = controller.signal;

// 2. 发送 axios 请求(传入 signal)
axios.get('https://api.example.com/data', { signal })
  .then((response) => console.log('axios 请求成功:', response.data))
  .catch((err) => {
    // 3. 捕获取消异常
    if (axios.isCancel(err)) {
      console.log('axios 请求已取消:', err.message);
    } else {
      console.log('axios 请求失败:', err);
    }
  });

// 4. 取消请求(可传入取消原因)
setTimeout(() => {
  controller.abort('用户主动取消'); // 取消原因会作为 err.message
}, 1000);

方案 2:旧版 CancelToken(已废弃,不推荐)

axios 旧版通过 CancelToken 取消请求,目前已被 AbortController 替代,仅用于兼容旧代码:

import axios from 'axios';

// 1. 创建 CancelToken 实例
const CancelToken = axios.CancelToken;
let cancel; // 存储取消函数

// 2. 发送请求(通过 cancelToken 配置)
axios.get('https://api.example.com/data', {
  cancelToken: new CancelToken((c) => {
    cancel = c; // c 是取消函数,赋值给外部变量
  })
})
.then((response) => console.log('请求成功:', response.data))
.catch((err) => {
  if (axios.isCancel(err)) {
    console.log('请求已取消:', err.message);
  } else {
    console.log('请求失败:', err);
  }
});

// 3. 取消请求(调用 cancel 函数,可传入原因)
setTimeout(() => {
  cancel('用户主动取消');
}, 1000);

四、常见应用场景:取消重复请求

实际开发中,最常用的场景是「取消重复请求」(如用户快速点击按钮,多次发送同一请求,仅保留最后一次),核心思路是:

  1. 维护一个「请求缓存池」(对象 / Map),存储当前未完成的请求;
  2. 发送请求前,检查缓存池:若存在相同请求,先取消旧请求;
  3. 请求完成(成功 / 失败 / 取消)后,从缓存池移除该请求。

示例(axios + AbortController 实现重复请求取消):

import axios from 'axios';

// 缓存池:key 为请求标识(如 url + 参数),value 为取消函数
const requestCache = new Map();

/**
 * 发送请求(自动取消重复请求)
 * @param {Object} config - axios 请求配置
 * @returns {Promise} 请求结果
 */
function requestWithCancel(config) {
  // 1. 生成请求唯一标识(根据 url + method + 参数生成)
  const requestKey = `${config.method || 'get'}-${config.url}-${JSON.stringify(config.params || {})}`;

  // 2. 若存在重复请求,先取消旧请求
  if (requestCache.has(requestKey)) {
    const oldController = requestCache.get(requestKey);
    oldController.abort('取消重复请求');
    requestCache.delete(requestKey); // 移除旧请求缓存
  }

  // 3. 创建新的 AbortController
  const controller = new AbortController();
  config.signal = controller.signal; // 绑定信号

  // 4. 缓存当前请求的取消控制器
  requestCache.set(requestKey, controller);

  // 5. 发送请求,完成后移除缓存
  return axios(config)
    .finally(() => {
      // 请求完成(成功/失败/取消),从缓存池移除
      if (requestCache.get(requestKey) === controller) {
        requestCache.delete(requestKey);
      }
    });
}

// 测试:快速调用两次相同请求,第一次会被取消
requestWithCancel({ method: 'get', url: 'https://api.example.com/data' });
requestWithCancel({ method: 'get', url: 'https://api.example.com/data' });

五、关键注意事项

  1. 取消时机:仅能取消「未完成的请求」(处于 pending 状态),已完成(200 OK)或已终止(网络错误)的请求无法取消;

  2. 服务器端状态:客户端取消请求后,服务器可能仍在处理该请求(只是客户端不再接收响应),若需真正终止服务器逻辑,需配合后端接口(如基于令牌的取消机制);

  3. 内存泄漏:取消请求后,需清理相关资源(如移除事件监听、删除缓存池中的请求),避免内存泄漏;

  4. 兼容性

    • AbortController:现代浏览器 / Node.js 15+ 支持,IE 不支持;
    • XMLHttpRequest.abort():支持所有浏览器(包括 IE);
    • 若需兼容 IE,优先使用 XMLHttpRequest 或 axios 旧版 CancelToken

总结

请求方式取消方案优点缺点
XMLHttpRequestxhr.abort()兼容性最好(支持 IE)语法繁琐,功能较少
fetch APIAbortController原生无依赖,语法现代不支持 IE,需捕获 AbortError
axiosAbortController(推荐)语法简洁,支持拦截器、取消重复请求需引入第三方库
axiosCancelToken(废弃)兼容旧代码已被标准废弃,不推荐新使用

推荐实践

  • 现代项目(不兼容 IE):优先使用 fetch API + AbortController 或 axios + AbortController
  • 需兼容 IE:使用 XMLHttpRequest
  • 实际开发中,结合「请求缓存池」实现重复请求取消,提升用户体验。