竞态问题 + axios 取消请求

目录

1,问题描述

在日常开发中,可能会有以下场景:

有多个 tab 页,每个 tab 是一个过滤条件,比如订单的状态:【已取消】,【待支付】,【已完成】。而 tab 对应的内容区域是同一个元素,显示列表。

问题:当快速切换 tab 时,如果保证拿到当前 tab 的数据?

比如当前 tab 是【已取消】,切换到【待支付】后快速切换到【已完成】,并且因为网络问题【已完成】的数据先回来,【待支付】的数据后回来,此时展示就出问题了。

这就是一个请求竟态问题

2,解决

1,丢弃错误的结果

通过标识符来保证,请求前和请求后的 id 相同。

js 复制代码
import { ref } from "vue";
import { getArticleList } from "./api/apiList";

let tabIndex = ref(0);
const changeTabIndex = (index) => {
	tabIndex.value = index;
	// 其他业务...
	getList();
};

const getList = async () => {
	try {
		const curIndex = tabIndex.value;
		const data = await getArticleList();
		if (curIndex !== tabIndex.value) {
			return;
		}
		console.log("获取正确的数据");
	} catch (error) {
		console.log(error);
	}
};
js 复制代码
// ./api/apiList
import axios from "./index";

export const APIS = {
	GET_ARTICLE_LIST: "/api/list",
};
export const getArticleList = () => {
	return axios.get(APIS.GET_ARTICLE_LIST);
};

2,取消之前的请求

当切换新 tab 页时,之前的请求就没有必要继续了,为了提升效率,可以取消掉已经发送的请求。

axios 官方文档 有介绍如何取消发起的请求,代码很简单:

js 复制代码
// 引入 axios 后,就会有这个构造函数。
const controller = new AbortController();

axios
	.get("/foo/bar", {
		signal: controller.signal,
	})
	.then(function (response) {
		//...
	});
// 取消请求
controller.abort();

CancelToken 的方式,axios 在 v0.22.0 就已经弃用了,应该用上面的新方式。

这样的话,我们可以在请求拦截器中增加判断。

js 复制代码
import axios from "axios";

let needCancelRequests = {};

// 添加请求拦截器
axios.interceptors.request.use(
	function (config) {
		const controller = new AbortController();
		config.signal = controller.signal;
        // 这里的标识符比较简单,可根据具体业务增加复杂性来保证唯一。
		needCancelRequests[config.url] = controller;
		return config;
	},
	function (error) {
		return Promise.reject(error);
	}
);

// 添加响应拦截器
axios.interceptors.response.use(
	function (response) {
		// 删除已完成的
		if (needCancelRequests[response.config.url]) {
			delete needCancelRequests[response.config.url]
		}
		return response;
	},
	function (error) {
		// 删除已完成的
		if (needCancelRequests[error.config.url]) {
			delete needCancelRequests[error.config.url]
		}
		if ((error.name = "CanceledError")) {
			console.log("已取消请求");
		}
		return Promise.reject(error);
	}
);

export { needCancelRequests };
export default axios;

在页面中使用

以上面的例子来说,修改这个方法,在请求之前停掉上个 tab 页发起的请求:

js 复制代码
import { needCancelRequests } from "./api/index";

const changeTabIndex  = (index) => {
    if (needCancelRequests[APIS.GET_ARTICLE_LIST]) {
		needCancelRequests[APIS.GET_ARTICLE_LIST].abort();
	}
    tabIndex.value = index;
    getList();
};

3,其他

1,有了以上的逻辑,我们也可以做到切换页面(路由)时,取消上个页面还在请求中的请求。

具体实现:

js 复制代码
// 添加请求拦截器
axios.interceptors.request.use(
	function (config) {
		const controller = new AbortController();
		config.signal = controller.signal;
		// 这里增加更多的标识,到时在全局路由守卫中,循环过滤上个路由的所有请求,逐个取消即可。
		needCancelRequests[config.url] = {
            'controller': controller,
            router: 'xxx'
        };
		return config;
	},
	function (error) {
		return Promise.reject(error);
	}
);

4,潜在的问题

上面使用的 needCancelRequests[config.url] 作为标识符,但其实问题很大,上面只是介绍的最简单的情况。更复杂的情况如下:

  1. 多个接口的方法名相同,但请求方式不同
  2. config.url包括 query 参数的,所以在页面中手动取消时,就不能使用已经定义好的不带的 query 参数的 url 作为 key。就是这里:
js 复制代码
const changeTabIndex  = (index) => {
    // APIS.GET_ARTICLE_LIST 并不带 query 参数,但在请求拦截器中的 key 却带着 query 参数。
    if (needCancelRequests[APIS.GET_ARTICLE_LIST]) {
		needCancelRequests[APIS.GET_ARTICLE_LIST].abort();
	}
};

所以,具体情况还需要具体分析。比如可以把方法名也拼上,或把自定义参数(应该定义一个字典保存)作为配置项传入,

js 复制代码
export const getArticleList = () => {
    // get 请求的第2个参数就是配置项,在拦截器中可以取到。
	return axios.get(APIS.GET_ARTICLE_LIST, { a: 1123 });
};

以上。

相关推荐
xing25165 分钟前
pytest-html
前端·html·pytest
茂茂在长安15 分钟前
Linux 命令大全完整版(11)
java·linux·运维·服务器·前端·centos
Violet51516 分钟前
ECMAScript规范解读——this的判定
javascript
知识分享小能手1 小时前
Html5学习教程,从入门到精通,HTML5 简介语法知识点及案例代码(1)
开发语言·前端·javascript·学习·前端框架·html·html5
IT、木易1 小时前
大白话React第二章深入理解阶段
前端·javascript·react.js
晚安7201 小时前
Ajax相关
前端·javascript·ajax
图书馆钉子户1 小时前
怎么使用ajax实现局部刷新
前端·ajax·okhttp
bin91531 小时前
DeepSeek 助力 Vue 开发:打造丝滑的单选按钮(Radio Button)
前端·javascript·vue.js·ecmascript·deepseek
qianmoQ1 小时前
第五章:工程化实践 - 第五节 - Tailwind CSS 常见问题解决方案
前端·css
那就可爱多一点点2 小时前
超高清大图渲染性能优化实战:从页面卡死到流畅加载
前端·javascript·性能优化