comenzar
- Últimamente me he encontrado con un problema con frecuencia, ¿
axios
cómo cancelar la solicitud? - Este artículo comenzará desde la perspectiva de un novato Xiaobai, desde el uso básico de la lógica de
axios
cancelación hasta el análisis de principios, y lo llevará a comprender y dominar a fondoaxios
el "secreto" de las solicitudes de cancelación en . - Tiempo de escritura: 2023/02/24-23/14
- Escrito por: lazy_tomato
1. Información oficial
1.1 Dirección de origen
github-axios Haga clic para saltar
1.2 axios La versión predeterminada actual de npm es 1.3.3
-
Cuando leí
axios
el código fuente , la versión predeterminada aún era0.28.x
, pero ha1.x.x
cambiado a , por lo que debe prestar atención a esto. -
La razón por la que me preocupa más
axios
el número de versión es que he visto esta oración:axios 的实现原理非常简单。 但是 axios 的精髓在于它已经迭代了40个版本,但是大版本号使用为0. npm 的 version 规则是首个版本号变化表示 api 不向下兼容。 而 axios 增加了这么多功能。却始终保持没有 api 明显变化。 这里 axios 内部使用了多种设计模式和架构模式。 (适配器,桥接,代理,抽象工厂,微内核设计。)
-
axios
El código fuente general tiene solo mil líneas, y todavía hay muchos lugares para imitar y aprender.
1.3 API para cancelar solicitudes en axios
En la captura de pantalla anterior, se puede ver que se recomienda la API de solicitud de cancelación axios
más reciente AbortController
. CancelToken
Las versiones anteriores v0.22.0
quedan obsoletas después de .
Este artículo explica principalmente
CancelToken
y las instruccionesAbortController
relacionadas se complementarán en blogs posteriores.
1.3.1 Cancelar token
ejemplo oficial
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// handle error
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');
1.3.2 Controlador de cancelación
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) {
//...
});
// cancel the request
controller.abort()
2. Caso de uso de CancelToken
Los casos de demostración en los documentos oficiales no son muy intuitivos. Construyamos un servicio y depurémoslo localmente.
2.1 Dependencias de instalación
npm init -y
npm i [email protected] express body-parser
# 注意,需要手动指定 axios 的版本,默认安装的axios是1.3.x版本的。
2.2 Código de servicio de back-endmain.js
const express = require('express')
const bodyParser = require('body-parser')
const app = express()
const router = express.Router()
// 跨域请求处理
app.all('*', (req, res, next) => {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Headers', 'X-Requested-With')
res.header(
'Access-Control-Allow-Headers',
'Content-Type, Content-Length, Authorization, Accept, X-Requested-With, X_Requested_With'
)
res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS')
//允许接收的请求头上加上一个Authorization,这样我们才能够将数据发送过去
res.header('X-Powered-By', '3.2.1')
// OPTIONS类型的请求 复杂请求的预请求
if (req.method == 'OPTIONS') {
res.send(200)
} else {
/*让options请求快速返回*/
next()
}
})
// 挂载处理post请求的插件
app.use(bodyParser.urlencoded({
extended: false }))
app.use(bodyParser.json())
router.get('/', (req, res) => {
res.send('Hello World')
})
// 五秒后返回
router.get('/tomato', (req, res) => {
setTimeout(() => {
res.send({
text: '我是lazy_tomato',
})
}, 5000)
})
router.post('/lazy', (req, res) => {
console.log(req.body)
setTimeout(() => {
res.send({
name: req.body.name + 'tomato',
})
}, 5000)
})
// 挂载路由
app.use(router)
// 监听5000端口 启动服务
app.listen('5000', () => {
console.log('Server is running 5000')
})
2.3 Iniciar servicio local
node ./main.js
2.4 Código front-end index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>lazy_tomato出品</title>
</head>
<body>
<h1 id="sendGet">发送get请求</h1>
<h1 id="sendPost">发送post请求</h1>
<h1 id="cancel">取消请求</h1>
<script src="./node_modules/axios/dist/axios.min.js"></script>
<script>
var sendGet = document.getElementById('sendGet')
var sendPost = document.getElementById('sendPost')
var cancel = document.getElementById('cancel')
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
sendPost.addEventListener('click', () => {
console.log('开始发送请求post请求')
axios.post('http://localhost:5000/lazy', {
name: '我是 lazy-'
}, {
cancelToken: source.token
}).then(res => {
console.log(res)
}).catch(err => {
if (axios.isCancel(err)) {
console.log('请求取消', err);
} else {
console.log('其他错误', err)
}
});
}, true)
sendGet.addEventListener('click', () => {
console.log('开始发送get请求')
axios.get('http://localhost:5000/tomato', {
cancelToken: source.token
}).then(res => {
console.log(res)
}).catch(err => {
if (axios.isCancel(err)) {
console.log('请求取消', err);
} else {
console.log('其他错误', err)
}
});
}, true)
cancel.addEventListener('click', () => {
console.log('开始终止请求')
source.cancel('手动调用 source.cancel方法,手动取消请求');
}, true)
</script>
</body>
</html>
2.5 Visualización de efectos
2.5.1 Cancelar la solicitud de obtención
2.5.2 Cancelar solicitud de publicación
2.5.3 Iniciar una solicitud de nuevo después de la cancelación
2.6 Resumen de uso de CancelToken
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// handle error
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');
En comparación con el ejemplo de uso oficial, resuma:
- Independientemente de si se trata de
get
una solicitud opost
una solicitud, se pasa unacancelToken
propiedad . - Cuando queramos cancelar la llamada de la interfaz
source
,cancel
simplemente llame al método. - Las llamadas repetidas cancelarán directamente la interfaz.
3. Código fuente correspondiente
Si no está interesado en el código fuente, puede pasar directamente al 4 y consultar la conclusión.
3.1 CancelarToken.fuente()
En comparación con nuestro caso de uso, la lógica central es CancelToken.source()
. El código fuente es el siguiente:
CancelToken.source()
El valor devuelto es en realidad un objeto, que contiene token
dos cancel
propiedades.
/**
* Returns an object that contains a new `CancelToken` and a function that, when called,
* cancels the `CancelToken`.
* 返回一个对象,其中包含一个新的 'CancelToken' 和一个函数,当调用时,
* 取消' CancelToken '
*/
CancelToken.source = function source() {
var cancel
// 在 new CancelToken的时候,传入一个函数 executor;将这个函数接收到的参数存储到 cancel 中
var token = new CancelToken(function executor(c) {
cancel = c
})
// source 其实就是一个对象
return {
token: token,
cancel: cancel,
}
}
-
token
Es una instanciaCancelToken
del (para obtener información específica, consulteCancelToken
el constructor); -
cancel
new CancelToken
esexecutor
el parámetro formal recibido por la función entrante en el proceso;
Después de leer esto, la lógica principal está en la función
CancelToken
.
3.2 Descripción general de CancelToken
Captura de pantalla del código general:
3.3 ¿Qué significa el atributo cancel en la tienda de origen?
CancelToken
¿Qué parámetros toma la función en vista executor
?
CancelToken 源码
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return
}
token.reason = new Cancel(message)
resolvePromise(token.reason)
})
所以 cancel 可以理解为
function cancel(message) {
if (token.reason) {
return
}
token.reason = new Cancel(message)
resolvePromise(token.reason)
}
调用逻辑
source.cancel('Operation canceled by the user.');
// 等同于
function cancel('Operation canceled by the user.') {
if (token.reason) {
return
}
token.reason = new Cancel('Operation canceled by the user.')
resolvePromise(token.reason)
}
3.4 Cancelar
new Cancel('Operation canceled by the user.')
El código fuente correspondiente arriba
// 一个普通的函数,包含一个 message 属性
function Cancel(message) {
this.message = message;
}
// 所以它返回的就是一个包含错误信息的对象
{
message: 'Operation canceled by the user.' }
3.5 Métodos en el prototipo CancelToken
/**
* Throws a `Cancel` if cancellation has been requested.
* 如果已请求取消,则抛出' Cancel '。 (我的理解:如果实例上存在 reason,报错)
*/
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason
}
}
/**
* Subscribe to the cancel signal
* 订阅取消标识
*/
CancelToken.prototype.subscribe = function subscribe(listener) {
// 存在错误,直接执行listener
if (this.reason) {
listener(this.reason)
return
}
// 将 listener 以数组的形式存储在当前实例的 _listeners 上
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)
}
}
En esta sección, analizamos los métodos en CancelToken
el prototipo :
throwIfRequested
: arroja un error si el atributo de cancelación está presente.subscribe
: Almacene los parámetros entranteslistener
;unsubscribe
: Eliminar los parámetros entranteslistener
;
3.6 Uso compartido completo del código CancelToken
CancelToken
Combinación de lógica global
function CancelToken(executor) {
// 1.如果传入的参数不是函数,直接报错。
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.')
}
// 2.定义一个变量
var resolvePromise
// 3.在实例上添加一个 promise 属性,等于一个 Promise对象
this.promise = new Promise(function promiseExecutor(resolve) {
// 4. 将 Promise 中的 resolve 函数暴露出去,存储到 resolvePromise。
resolvePromise = resolve
})
// 5. token 为当前 CancelToken 实例对象
var token = this
// 6. this.promise 成功后,批量调用 token._listeners 每一项。
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
})
// 7. 更换 promise 的 then 方法
this.promise.then = function (onfulfilled) {
var _resolve
var promise = new Promise(function (resolve) {
token.subscribe(resolve)
_resolve = resolve
}).then(onfulfilled)
promise.cancel = function reject() {
token.unsubscribe(_resolve)
}
return promise
}
// 8. 处理 executor 的形参
executor(function cancel(message) {
if (token.reason) {
return
}
token.reason = new Cancel(message)
resolvePromise(token.reason)
})
}
Descripción de la dificultad:
- Pasos 3 y 4:
this.promise
almacene unPromise
objeto yPromise
el estado del objeto seráresolvePromise
controlado . - Paso 5:
token
El atributo es igual al objeto de instancia actual. - Pasos 6 y 7: Estas dos partes son un poco más complicadas. Explicar con detalle.
El paso 6 this.promise
define el método que se activa en caso de éxito.
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
}
El paso 7 this.promise
define el método que se dispara en caso de éxito.
function (onfulfilled) {
var _resolve
var promise = new Promise(function (resolve) {
token.subscribe(resolve)
_resolve = resolve
}).then(onfulfilled)
promise.cancel = function reject() {
token.unsubscribe(_resolve)
}
return promise
}
Pasos 6 y 7, si se invierte el orden, los resultados serán completamente diferentes.
Lógica de ejecución del código fuente: Paso 6, primero registra una microtarea en la cola. Luego, el paso 7
Promsie
cambiathen
el método del objeto actual.
resolvePromise
Cuando , solo se ejecutará el paso 6, pero se juzgará si el objeto devuelto porthen
el método esPromsie
un objeto, y si esPromsie
un objeto se ejecutará el paso 6, de lo contrario, se omitirá.
3.7 solicitar xhr.js
Actualmente axios
admite XMLHttpRequest
y http
dos formas de enviar solicitudes.
Este artículo se centra en ello XMLHttpRequest
.
var request = new XMLHttpRequest()
if (config.cancelToken || config.signal) {
onCanceled = function (cancel) {
if (req.aborted) return
req.abort()
reject(!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel)
}
config.cancelToken && config.cancelToken.subscribe(onCanceled)
if (config.signal) {
config.signal.aborted
? onCanceled()
: config.signal.addEventListener('abort', onCanceled)
}
}
Se crea una instancia XMLHttpRequest
de . Si está presente cancelToken
, active activamente config.cancelToken.subscribe(onCanceled)
el método . Método para almacenar onCanceled
cancelaciones CancelToken
en instancias de .
CancelToken
La instancia, el almacenamiento onCanceled
y las características del cierre utilizado aquí.
3.8 en Cancelado
function onCanceled(cancel) {
// 不存在 request 直接 return
if (!request) {
return
}
// 调用 取消的方法。
request.abort()
reject(cancel)
// Clean up request
request = null
}
Para cancelar la solicitud e implementarla en una implementación específica, en realidad es: request.abort()
, eso esXMLHttpRequest.abort()
Documentación oficial de MDN - XMLHttpRequest.abort()
3.9 Si cancelo una solicitud, ¿todas las solicitudes posteriores se cancelarán automáticamente?
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
if (config.signal && config.signal.aborted) {
throw new Cancel('canceled');
}
}
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};
Antes de que se inicie cada solicitud, la función de verificación se activará al procesar la configuración:throwIfCancellationRequested
Si el parámetro pasado existe cancelToken
y está en cancelToken
la instancia reason
se informará directamente de un error.
4. Resumen
En dos explicaciones:
4.1 uso
La forma de usarlo es pasar un cancelToken: CancelToken.source().token
token .
Se activa manualmente cuando es necesario cancelar la solicitudCancelToken.source().cancel
4.2 Principio
-
CancelToken.source()
devolverá un objetosource
; -
source
Hay dos propiedades en el objeto:token,cancel
;
{
"source.token":"存储 CancelToken 的实例对象 A",
"source.cancel":"存储可以改变 A.promise 状态的函数"
}
-
A también almacena el método b para cancelar la solicitud actual;
-
A.promise
Cuando cambie el estado, se llamará al método b; -
Cuando queramos cancelar activamente la solicitud, llame
source.cancel
= "cambiarA.promise
estado = = llamar al método b;
**Lógica central: **Crear objetos usando funciones de fábrica. Usando las características del cierre, almacene el método de cancelación de la solicitud en el objeto y llámelo externamente.
5. pensar
En los últimos días, estuve axios
estudiando el código lógico de cancelación de ; a partir de hoy, finalmente aprendí y escribí documentos relacionados.
¿Resumir lo que he ganado?
5.1 En la dirección general:
- Comprender el uso de la solicitud
axios
de cancelación ; - Comprender la solicitud
axios
de cancelación , las diferentes implementaciones de las dos versiones;
5.2 Mejoras específicas:
- En el código fuente, el estado de un
Promise
objeto está controlado por variables externas, que pueden ser imitadas; - En comparación con nuestra programación habitual orientada a procesos, incluida la realización de algunas funciones en el código fuente de Vue que verifiqué, todas están basadas en un constructor. Es extremadamente conveniente usar el "modo de fábrica" para inicializar por lotes algunos objetos con el mismo comunalidad. de. Quizás en mi programación posterior, aprenderé gradualmente a intentar usar este método.
- En un
CancelToken
código fuente , vale la pena reflexionar sobre la lógica de cancelar las solicitudes de notificaciones por lotes. Un atributo de instancia que almacena varios objetos suscritos y agrupa notificaciones al cancelar solicitudes. (Algo similar a la idea de publicar y suscribirse)
fin
blog relacionado con axios