对比终止接口请求的两种方法,AbortController和CancelToken

1,913 阅读5分钟

前言

在真实的项目中其实很多时候都有接口请求已经发出,但还没响应完成就要取消请求的业务场景。

有可能这些场景我们不做处理也不影响功能正常使用,但如果做接口终止处理客户体验会更好并且也节省了服务器资源,如下几个常见的业务场景:

(1)页面快速切换时: 页面初始化时请求的接口没必要继续请求应当终止

(2)表格分布切换时: 用户快速切换分页会重复请求相同接口,这时就当终止前面请求

(3)文件上传时: 如果是大文件持续上传,就当有终止上传按钮

(4)连续快速触发请求: 例如在搜索输入框中动态获取内容,输入太快就终止前面的只保留最后一次请求

(5)超时处理: 例如设置超过30S还没有响应,就当终止请求,提示请求超时

(6)滚动加载: 如果滚动加载图片时用户滚动的超级快,那么就当只请求视口上下的内容,其它区域终止请求

如何取消请求?

主流取消接口请求的个人推荐方法有两种分别是AbortControllerCancelToken。这两种方法各有优劣,具体对比如下。

兼容性: AbortController不支持IE浏览器,因此CancelToken兼容性更好。

规范化: AbortControllerW3C 标准的一部分,而CancelToken是主流库存axios 的一部分。

集成性: AbortController可以非常方便地和各种主流前端框架结合使用更加灵活,而CancelToken仅限于 axios 库。

控制度: AbortController能更精细控制操作,例如可以通过 signal 对象去检查取消状态,但CancelToken就较简单,灵活性相对较低,仅仅提供了基本的取消功能。

综上所述,我们可以根据具体的项目情况去选择合适的工具去处理.

下面我将以Vue框架(其它框架用法也一样的)为例分别讲解AbortControllerCancelToken的具体用法。

AbortController具体用法

简述: AbortController 其实是用于取消异步的一个 JavaScript APIAbortController允许我们在任何时候都能取消正在进行的异步请求操作,合理使用能避免不必要的资源消耗。

AbortController用法主要由两个部分组成:

  • AbortController :这部分用于创建和管理取消命令。
  • Signal :用于传递取消命令。

基本用法

下面上一个完整的基本使用示例

<template>
  <div>
    <button @click="cancel">取消请求</button>
    <button @click="getData">请求接口</button>
  </div>
</template>

<script setup>
import axios from 'axios';
import { onUnmounted, ref } from 'vue';

// 组件卸载时需要清理控制器,否则容易引起内存泄漏
onUnmounted(() => {
  if (myController.value) {
    myController.value.abort();
  }
});

// 定义控制器
const myController = ref(null);

// 取消请求
const cancel = () => {
  if (myController.value) {
    myController.value.abort();
    console.log('请求已取消');
  }
};

// 发送请求
const getData = () => {
  // 如果有正在请求,则取消之前的请求
  if (myController.value) {
    myController.value.abort();
  }

  // 创建一个新的 AbortController
  myController.value = new AbortController();
  const signal = myController.value.signal;

  // 请求接口
  axios.get('/api/test/data', { signal })
    .then(res => {
      // 响应数据
    })
    .catch(error => {
      if (error.name === 'AbortError') {
        // 请求被取消
      } else {
        // 错误
      }
    });
};
</script>

代码解释: 创建一个myController用于存储 AbortController 实例引用变量。点击取消请求或者有新的请求时,如果 myController 存在,则调用 abort() 方法取消请求。取消之后创建一个新的 AbortController 实例,并且获取 signal作为请求的取消信号。

注意: 组件卸载时记得要在onUnmounted生命周期中清理控制器,避免出现内存泄漏。

进阶用法

上面是针对业务中某个小场景基本使用方法,我们可以结合拦截器进行封装。

具体思路:对每个请求都用MapAbortControllerurl以键值对的形式保存起来,只要经过请求拦截器就判断当前url是否正在请求中,如果是就移除正在请求的url然后重新执行。

以下提供思路,如果在真实项目要结合拦截器封装。

const pendingMap = new Map();

// 添加请求 函数,把正在请求的url保存起来
function addPending(config) {
  this.removePending(config);
  const url = getPendingUrl(config);
  const controller = new AbortController();
  config.signal = config.signal || controller.signal;
  if (!pendingMap.has(url)) {
    // 如果当前请求不在等待中,将其添加到等待中
    pendingMap.set(url, controller);
  }
}

// 移除请求 函数
function removePending(config) {
  const url = getPendingUrl(config);
  if (pendingMap.has(url)) {
    const controller = pendingMap.get(url);
    controller.abort();
    pendingMap.delete(url);
  }
}

function getPendingUrl(config) {
  return [config.method, config.url, JSON.stringify(config.params), JSON.stringify(config.data)].join('&');
}

解释:addPending添加请求之前,先调用removePending把可能要终止的请求进行终止,然后把AbortControllerurl以键值对的形式保存起来。

CancelToken具体用法

简述: CancelTokenAxios 库本身提供的用于取消请求的一个方法,局限于Axios 库。CancelToken不需要手动处理 AbortController可以更方便地管理接口请求的取消。

<template>
  <div>
    <button @click="myCancel">取消请求</button>
    <button @click="getData">请求接口</button>
  </div>
</template>

<script setup>
import axios from 'axios';
import { onUnmounted, ref } from 'vue';

// 组件卸载时需要清理控制器,否则容易引起内存泄漏
onUnmounted(() => {
  if (myCancelSource.value) {
    myCancelSource.value.cancel('组件卸载');
  }
});

// 定义控制器
const myCancelSource = ref(null);

// 取消请求
const myCancel = () => {
  if (myCancelSource.value) {
    myCancelSource.value.cancel('请求被取消');
  }
};

// 发送请求
const getData = () => {
  // 如果有正在请求,则取消之前的请求
  if (myCancelSource.value) {
    myCancelSource.value.cancel('请求被取消');
  }

  // 创建一个新的 CancelToken
  myCancelSource.value = axios.CancelToken.source();

  // 请求接口
  axios.get('/api/test/data', { cancelToken: myCancelSource.value.token })
    .then(res => {
      // 响应数据
    })
    .catch(error => {
      if (axios.isCancel(error)) {
        // 请求被取消
      } else {
        // 错误
      }
    });
};
</script>

代码解释: 用法跟上面的AbortController有点类似,先创建一个引用变量myCancelSource,每次进行请求就先判断是否正在请求,如果是就用.cancel终止,然后用axios.CancelToken.source()创建一个新的 CancelToken

小结

其实我个人更推荐AbortController其一是因为AbortController更规范并且更灵活好用适用范围更广,其二是因为就算CancelToken.source()了,也是会对后端产生了一次完整请求,只是前端不等待返回内容处理而已,后端资源压力还是给到了。

除了上面列出的业务场景外肯定还有很多其它应用场景适用,有时甚至和防抖节流应用场景比较接近,具体就要看对应的项目灵活选择解决方案了。

好啦先写到这里,如果哪里写的不对或者有好的建议欢迎大佬们指出。