知识点-axios之CancelToken原理揭秘

159 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。
——Bug修复员-鲁宽宽

背景

 在开发过程中,不免会遇到当同样的接口被请求多次时导致页面展示可能不符合预期的场景。造成此现象的原因也很简单,如下图:

图一

 上图的场景为一个有筛选tab的列表页面。用户首次点击了待发货,其次瞬间又点击了已发货tab, 发出了两个列表请求。

  期望的列表内容是已发货的数据,不过由于接口延迟等,导致列表展示的内容为待发货数据,造成页面显示内容bug。

  解决此问题的方式有很多种,这篇探讨的方案是:使用axios取消重复请求接口方案。

axios之CancelToken设计

  大家都知道axios的CancelToken属性可以取消请求,不过CancelToken是如何实现的的?列举几点我之前的疑惑点:

1、CancelToken 构造函数的入参为什么是函数?
2、何时更改CancelToken内promise状态?
3、何时阻断当前请求?

  带着问题去验证会加深记忆与理解,下图是我总结的CancelToken整体的生命周期图。

图二

  关键节点代码:

// 抛给业务方执行的函数
executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }

    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });


// 请求前执行dispatch
/**
 * Throws a `Cancel` if cancellation has been requested.
 */
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }
}

// 制造浏览器错误
/**
 * Throws a `Cancel` if cancellation has been requested.
 */
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason;
  }
};

  从上图可以发现最重要的节点是:触发 执行取消请求 节点,先来看下此节点相关的代码。

axios.interceptors.request.use((config) => {
    let cancel;
    config.cancelToken = new CancelToken((c) => {
        cancel = c;
    })
    cancel('取消请求');
    return config;
})

  当理解了取消拦截的基本原理,那么接下来需要做的就是上层业务封装,使其可以满足当前业务诉求。

axios之业务实现设计

  这里讲一下目前我所在的业务对于axios的设计。主要分为三点:

  • 取消前者所有请求,保留最后一次请求
  • 保留首次请求,取消后面所有请求
  • 不拦截任何请求

主要思路是如何在请求的时候对当前url做一些判断,使其满足以上三个场景。

  整体思路如下图:

图三

  关键实现代码:

// 定义全局数组
const requestApiList = [] // 请求中的api

// 执行类别
let cancelType = 2; // 1 首次请求有效  2:最后一次请求有效  3:不做处理

// 核心逻辑
const cancelHandle = (config, c, cancelType) => {
    if (!config) return;
    var url = config.url;
    var index = requestApiList.findIndex((i) => {
        return i.url === url;
    });
    // 首次请求有效,取消后面所有请求
    if (cancelType === 1) {
        if (index > -1) {
            // 后面匹配到的请求,执行取消
            c ? c('数据请求中,请稍后') : requestApiList.splice(index, 1);
        } else {
            // 向数组推入一个当前url记录
            requestApiList.push({
                url: url,
                c: c
            });
        }
    }
    // 最后一次请求有效,取消前面所有请求
    if (cancelType === 2) {
        if (index > -1) {
            // 将最早推入的记录进行取消请求
            c && requestApiList[index].c('数据请求中,请稍后');
            requestApiList.splice(index, 1);
        }
        // 每次推入一条记录
        c && requestApiList.push({
            url: url,
            params: config.params,
            c: c
        });
    }
}

  每个业务的使用场景不同,以上代码可以作为参考使用。

  实际使用过程中还需要考虑如何清空已执行的url记录,可以将答案打在评论区以作他人参考使用~。

结语

以上只是个人整理的小知识,若有错误可以及时回复,我会及时修正。

很开心~,又生产了一篇文章,其中最大的收获就是收集资料的过程。