前提准备

首先编写一个测试接口,模拟接口延迟返回,请求5秒后再返回结果

使用 node + koa 来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const Router = require("koa-router");
const router = new Router();

router.get("/getLongData", async (ctx, res) => {
// 获取get请求的参数
// console.log(ctx.query);

await new Promise((resolve) => {
setTimeout(() => {
resolve();
}, 5000);
});

ctx.body = {
code: 200,
message: "请求成功!",
};
});

module.exports = router;

AbortController

使用 AbortController

AbortController 是一个浏览器提供的 API,用于取消正在进行的异步操作,如 Fetch 请求或 Axios 请求。你可以创建一个 AbortController 实例,并在 Axios 请求配置中通过 signal 属性传递它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import axios from 'axios';

const controller = new AbortController();

axios.get('/foo/bar', {
signal: controller.signal
}).then(response => {
// 处理响应
}).catch(error => {
if (error.name === 'AbortError') {
console.log('请求被取消');
} else {
console.error('发生了一个错误:', error);
}
});

// 取消请求
controller.abort();

CancelToken

CancelToken 是 Axios 自带的一个类,用于实现请求取消功能。你需要创建一个 CancelToken 实例,并在请求配置中通过 cancelToken 属性传递它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import axios from 'axios';
import CancelToken from 'axios/cancelToken';

let cancel;

// 发起请求
axios.get('/foo/bar', {
cancelToken: new CancelToken(c => cancel = c)
}).then(response => {
// 处理响应
}).catch(error => {
if (axios.isCancel(error)) {
console.log('请求被取消');
} else {
console.error('发生了一个错误:', error);
}
});

// 取消请求
if (cancel) {
cancel('取消请求的原因');
}

全局取消请求封装

新建一个 globalCancelToken.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 最新的一个请求
let cancel = null

// 所有请求
let cancelTokenList = []

function setToken(cancelToken) {
cancel = cancelToken
cancelTokenList.push(cancelToken)
}

function cancelToken() {
cancel && cancel()
cancelTokenList.pop()
}

function clearAllToken() {
while (cancelTokenList.length > 0) {
let cancel = cancelTokenList.pop()
console.log(cancel, 'cancel')
cancel && cancel()
}
}

export {
setToken,
cancelToken,
clearAllToken,
}

这个文件中定义了一个变量,一个数组,cancelTokenList 用于存放每一次请求所对应的 cancelToken

在请求拦截器中添加取消请求的配置,相应拦截器中判断错误类型是否为取消请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import { setToken } from '@/utils/globalCancelToken.js'

// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时
timeout: 100000,
})

// request拦截器
service.interceptors.request.use(config => {
// 省略其他配置....

// 添加可取消请求配置
config.cancelToken = new axios.CancelToken(c => setToken(c))

return config
}, error => {
console.log(error)
Promise.reject(error)
})


// 响应拦截器
service.interceptors.response.use(res => {
// 未设置状态码则默认成功状态
const code = res.data.code || '0'
// 获取错误信息
const msg = errorCode[code] || res.data.message || errorCode['default']
// 二进制数据则直接返回
if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {
return res.data
}
if (code === 401 || code === '10006') {
if (!isRelogin.show) {
isRelogin.show = true
ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
isRelogin.show = false
useUserStore().logOut().then(() => {
location.href = '/index'
})
}).catch(() => {
isRelogin.show = false
})
}
return Promise.reject('无效的会话,或者会话已过期,请重新登录。')
} else if (code === 500) {
ElMessage({ message: msg, type: 'error' })
return Promise.reject(new Error(msg))
} else if (code !== '0') {
ElMessage({ message: msg, type: 'error' })
return Promise.reject('error')
} else {
return Promise.resolve(res.data)
}
},
error => {
if (error.name === 'CanceledError') {
console.log('请求已取消')
return Promise.reject('请求已取消')
}
ElMessage({ message: message, type: 'error', duration: 5 * 1000 })
return Promise.reject(error)
},
)

编写测试页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template>
<div>
<el-button type='primary' @click='send'>发起请求</el-button>
<el-button type='warning' @click='cancelSend'>取消最近一次请求</el-button>
<el-button type='warning' @click='cancelAllSend'>取消所有请求</el-button>
</div>
</template>

<script setup>
import { testAxiosServer } from '@/api/testApi.js'
import { cancelToken, clearAllToken } from '@/utils/globalCancelToken.js'

function send() {
testAxiosServer().then(res => {
console.log(res)
})
}

function cancelSend() {
cancelToken()
}

function cancelAllSend() {
clearAllToken()
}

// 组件销毁前取消所有未完成的请求
onUnmounted(() => {
clearAllToken()
})
</script>

axios