axios 如何取消请求
axios 探索 如何取消 请求的
以 v0.x 版本为例 取消xhr 请求
先看看实际开发中,axios 是如何取消请求示例代码
import axios from 'axios';
const cancelToken = axios.CancelToken;
const source = cancelToken.source();
axios.interceptors.request.use( (config) => {
const xxx = config.headers.xxx;
if (!xxx) {
// 取消请求
config.cancelToken = source.token;
source.token.reason = {
message: '登录过期',
};
source.cancel('登录过期');
// 或者
config.cancelToken = new axios.CancelToken(function executor(c) {
c('登录过期')
})
}
return config
},
error => {
return Promise.reject(error)
}
)
再先看xhr源码 中如何处理取消请求
lib\adapters\xhr.js
- 发起ajax请求,
- 判断 请求配置 config 中是否存在 cancelToken 如果存在那就
- 调用 cancelToken.subscribe 方法,
- onCanceled 方法 作为参数 传入
onCanceled 中 request.abort() 即为 XHR 取消请求的方法
// 先判断 config.cancelToken 字段存在就会调用 config.cancelToken.subscribe 方法
if (config.cancelToken || config.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
onCanceled = function(cancel) {
if (!request) {
return;
}
reject(!cancel || (cancel && cancel.type) ? new CanceledError() : cancel);
request.abort();
request = null;
};
config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}
if (!requestData) {
requestData = null;
}
那我来具体看看 subscribe 做了什么事儿
可以看到 源码中,判断是否有取消的理由(即reason),存在 就会执行 listener (即外部传入的onCanceled),
lib\cancel\CancelToken.js
CancelToken.prototype.subscribe = function subscribe(listener) {
if (this.reason) {
listener(this.reason);
return;
}
if (this._listeners) {
this._listeners.push(listener);
} else {
this._listeners = [listener];
}
};
关键点就是 this.reason 什么时候有值?
lib\cancel\CancelToken.js
当执行executor 的时候 reason 会 被赋值
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new CanceledError(message);
resolvePromise(token.reason);
});
完整的看一下源码
从 new CancelToken 开始
-
给构造函数 CancelToken 原型上设置 subscribe 函数,用于添加订阅 监听器
-
给构造函数 CancelToken 原型上设置 unsubscribe 函数,用于删除指定的监听器
-
new CancelToken 传入一个 回调函数 (executor )
-
生成一个promise 对象
-
重写 该promise 对象的then 方法
-
给该promise 设置 then 回调函数
-
执行 executor 函数,传入 cancel 函数(即取消函数)
当我们 构造 CancelToken 实例(cancelToken) 传入 executor 函数,从executor中拿到 cancel 函数,执行 cancel函数,会导致 CancelToken 实例中,reaseon被赋值, promose对象 状态变为 fulfilled 状态,然后执行promise实例的then 回调, 执行内部所有 监听器 listeners,当发起xhr请求时判断 config.cancelToken 调用 subscribe 时,reason 已经被赋值了,就会直接执行 config.cancelToken(onCanceled) 中的 onCanceled 方法 取消 xhr 请求
lib\cancel\CancelToken.js
'use strict';
var CanceledError = require('./CanceledError');
/**
* A `CancelToken` is an object that can be used to request cancellation of an operation.
*
* @class
* @param {Function} executor The executor function.
*/
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
var token = this;
// eslint-disable-next-line func-names
this.promise.then(function(cancel) {
if (!token._listeners) return;
var i;
var l = token._listeners.length;
for (i = 0; i < l; i++) {
token._listeners[i](cancel);
}
token._listeners = null;
});
// eslint-disable-next-line func-names
this.promise.then = function(onfulfilled) {
var _resolve;
// eslint-disable-next-line func-names
var promise = new Promise(function(resolve) {
token.subscribe(resolve);
_resolve = resolve;
}).then(onfulfilled);
promise.cancel = function reject() {
token.unsubscribe(_resolve);
};
return promise;
};
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new CanceledError(message);
resolvePromise(token.reason);
});
}
/**
* Throws a `CanceledError` if cancellation has been requested.
*/
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};
/**
* Subscribe to the cancel signal
*/
CancelToken.prototype.subscribe = function subscribe(listener) {
if (this.reason) {
listener(this.reason);
return;
}
if (this._listeners) {
this._listeners.push(listener);
} else {
this._listeners = [listener];
}
};
/**
* Unsubscribe from the cancel signal
*/
CancelToken.prototype.unsubscribe = function unsubscribe(listener) {
if (!this._listeners) {
return;
}
var index = this._listeners.indexOf(listener);
if (index !== -1) {
this._listeners.splice(index, 1);
}
};
/**
* Returns an object that contains a new `CancelToken` and a function that, when called,
* cancels the `CancelToken`.
*/
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
module.exports = CancelToken;