本文详细解析前端axios取消请求的两种主要用法:CancelToken构造函数与source方法。通过源码追踪CancelToken、Cancel及xhr适配器,阐明基于Promise状态控制的取消机制,并给出封装axios后批量取消及路由切换时取消请求的实战方案。
应用场景
在前端开发中,取消HTTP请求是一项常见需求。以下是两个典型的工作场景:
- 在Tab切换刷新列表数据时,若多个Tab共用同一变量存储数据,且请求存在延迟,极易导致数据展示错乱。
- 在执行文件导出或下载操作时,用户可能需要中途取消任务。
如何取消请求
针对HTTP请求的取消,axios官方文档提供了两种实现方式:
第一种方式是使用 CancelToken。
const { CancelToken, isCanCel } = axios;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(thrown => {
if (isCancel(thrown)) {
// 获取 取消请求 的相关信息
console.log('Request canceled', thrown.message);
} else {
// 处理其他异常
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// 取消请求。参数是可选的,参数传递一个取消请求的相关信息,在 catch 钩子函数里能获取到
source.cancel('Operation canceled by the user.');
第二种方式是在构造函数 CancelToken 中传递一个 executor 函数。这种方式的优点在于,可以使用同一个 cancel token 来取消多个请求。
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// 参数 c 也是个函数
cancel = c;
})
});
// 取消请求,参数用法同上
cancel();
项目中用法示例
在实际项目中,通常会对 axios 进行二次封装,以统一处理请求、响应、状态码及业务 code。以下是一个常见的项目级 request.js 示例:
import axios from 'axios'
import store from '@/store'
import { getToken } from '@/utils/auth'
// 创建一个 axios 实例,并改变默认配置
const service = axios.create({
baseURL: process.env.BASE_API, // api 的 base_url
timeout: 5000 // request timeout
})
// 请求拦截
service.interceptors.request.use(
config => {
// Do something before request is sent
if (store.getters.token) {
// 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改
config.headers['X-Token'] = getToken()
}
return config
},
error => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
}
)
// 响应拦截
service.interceptors.response.use(
response => response,
error => {
alert(error)
return Promise.reject(error)
}
)
export default service
若需为特定请求添加取消功能,需在调用 API 时传入 cancelToken 选项。具体使用示例如下:
// api.js
import request from 'request'
export function getUsers(page, options) {
return request({
url: 'api/users',
params: {
page
},
...options
})
}
// User.vue
import { CancelToken, isCancel } from 'axios'
import { getUsers } from 'api'
...
cancel: null
...
toCancel() {
this.cancel('取消请求')
}
getUsers(1,
{
cancelToken: new CancelToken(c => (this.cancel = c))
}
)
.then(...)
.catch(err => {
if (isCancel) {
console.log(err.message)
} else {
...
}
})
通过上述方式,即可在封装好的 axios 中顺畅地取消特定请求。其核心原理在于调用 API 时传入 cancelToken 配置项,从而在业务代码中能够针对特定请求执行取消操作。
批量取消请求
前文提到的第二种方法指出,通过指定相同的 cancel token 可实现取消。但在上述项目示例中,难以直接获取到相同的 cancel token。我们可以换一种思路:使用数组保存每个需要取消的 cancel token,然后遍历数组逐一调用。具体实现如下:
// User.vue
import { CancelToken, isCancel } from 'axios'
import { getUsers } from 'api'
...
cancel: []
...
toCancel() {
while (this.cancel.length > 0) {
this.cancel.pop()('取消请求')
}
}
getUser1(1,
{
cancelToken: new CancelToken(c1 => (this.cancel.push(c1)))
}
)
getUser2(2,
{
cancelTokem: new CancleTokem(c2 => (this.cancel.push(c2)))
}
)
切换路由时,取消请求
前文已介绍如何取消单个请求及页面内批量 abort 的方法。此外,还有一种常见需求:在切换路由时取消所有请求。此处简要说明思路:在请求拦截器中统一添加 token,并设置全局变量 source 控制一个 cancel token,在路由变化时调用 cancel 方法即可。
http.interceptors.request.use(config => {
config.cancelToken = store.source.token
return config
}, err => {
return Promise.reject(err)
})
router.beforeEach((to, from, next) => {
const CancelToken = axios.CancelToken
store.source.cancel && store.source.cancel()
store.source = CancelToken.source()
next()
})
// 全局变量
store = {
source: {
token: null,
cancel: null
}
}
取消请求的实现原理
cancelToken 的 source 方法维护了一个对象,其中包含 token 令牌和 cancel 方法。token 源自构造函数 CancelToken,调用 cancel 方法后,token 内部的 promise 状态变为 resolved,进而触发 xhr 的 abort 方法,成功取消请求。接下来,我们将通过一个简单的取消请求示例来深入分析其实现过程:
var CancelToken = axios.CancelToken;
var source = CancelToken.source();
axios.get('/get?name=xmz', {
cancelToken : source.token
}).then((response)=>{
console.log('response', response)
}).catch((error)=>{
if(axios.isCancel(error)){
console.log('取消请求传递的消息', error.message)
}else{
console.log('error', error)
}
})
// 取消请求
source.cancel('取消请求传递这条消息');
这是一个简单的取消请求示例。我们从最开始的axios.CancelToken入手,前往axios/lib/axios.js文件进行查看。
axios.CancelToken = require('./cancel/CancelToken');
很快就能找到 CancelToken。在示例中调用了 source 方法,因此需要前往axios/lib/cancel/CancelToken.js文件查看该 source 方法的具体实现。
CancelToken.source = function(){
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c
})
return {
token : token,
cancel : cancel
}
}
source 方法逻辑简单,主要返回一个包含 token 和 cancel 属性的对象。由于 token 和 cancel 均通过 CancelToken 构造函数生成,因此需在该文件中向上查找 CancelToken 函数。
function CancelToken (executor){
// ...
// 判断executor是一个函数,不然就报错
var resolvePromise;
this.promise = new Promise(function(resolve){
resolvePromise = resolve;
})
var token = this;
// 以上token现在有一个promise属性,是一个未成功的promise对象;
executor(function cancel(message){
if(token.reason){
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
})
// 这个cancel函数就是 上面函数中的cancel,也就是source.cancel;
}
至此已知 source.cancel 是一个函数,souce.token 是一个实例化对象。暂且了解这些,继续查看文章开头的示例,接下来是发送请求。代码最后一行执行souce.cancel();,souce.cancel 即为触发取消请求的函数。现在回头查看前述 cancel 函数,它给 token 添加了一个 reason 属性。接下来查看这个 reason 属性的定义,即 Cancel 构造函数,位于axios/lib/cancel/Cancel.js文件中。
function Cancel(message){
this.message = message
}
Cancel 的实现非常简单,仅为实例化对象添加一个 message 属性。因此,现在 token.reason 是一个包含 message 属性的对象。继续回到 cancel 函数,resolvePromise 函数被执行,使得 token.promise 对象从原本未成功状态变为成功状态,并将 token.reason 对象作为参数传递。简单总结:执行取消函数,即让 token 的 promise 状态变为成功。此时分析似乎中断,promise 变为成功状态后如何取消请求?虽然当前同步代码执行完毕,但请求尚未发出,因此还需查看发送请求的函数。
在分析发送请求前,回顾开头示例,与普通 GET 请求相比,配置对象中多了一个 cancelToken 属性,其值为 token。该属性具体起何作用?前往axios/lib/adapters/xhr.js一探究竟(此处仅截取关于 cancelToken 的部分)。
// 在发送请求之前,验证了cancelToken,看来此处就是用来取消请求的;
if(config.cancelToken){
// 具体是如何取消的,是在这个判断内定义的;
config.cancelToken.promise.then(function(cancel){
request.abort();
reject(cancel);
request = null;
})
}
// 发送请求
request.send(requestData);
仔细观察,这只是一个 promise 的 then 函数,仅在 promise 状态变为成功后执行。前文分析表明,cancel 操作会使该 promise 状态变为成功。因此,若执行了取消请求函数,该 then 便会执行,从而取消发送请求,并将发送请求的 promise 变为 reject 状态,最终被 axiox.get().catch() 捕获。流程已清晰,最后总结:执行 cancel 使 token 的 promise 变为成功。在真正发送请求前,验证 token.promise 的状态是否已改变;若已改变,则取消请求。这便是取消请求背后的简单思想。
以上就是axios取消请求总结的详细内容,更多关于axios取消请求的资料请关注本站其它相关文章!