Cómo cancelar una solicitud en axios

comenzar

  • Últimamente me he encontrado con un problema con frecuencia, ¿ axioscó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 axioscancelación hasta el análisis de principios, y lo llevará a comprender y dominar a fondo axiosel "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

inserte la descripción de la imagen aquí

  • Cuando leí axiosel código fuente , la versión predeterminada aún era 0.28.x, pero ha 1.x.xcambiado a , por lo que debe prestar atención a esto.

  • La razón por la que me preocupa más axiosel número de versión es que he visto esta oración:

    axios 的实现原理非常简单。
    但是 axios 的精髓在于它已经迭代了40个版本,但是大版本号使用为0. 
    npm 的 version 规则是首个版本号变化表示 api 不向下兼容。
    而 axios 增加了这么多功能。却始终保持没有 api 明显变化。
    这里 axios 内部使用了多种设计模式和架构模式。 (适配器,桥接,代理,抽象工厂,微内核设计。)
    
  • axiosEl 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

inserte la descripción de la imagen aquí

En la captura de pantalla anterior, se puede ver que se recomienda la API de solicitud de cancelación axiosmás reciente AbortController . CancelToken Las versiones anteriores v0.22.0quedan obsoletas después de .

Este artículo explica principalmenteCancelToken y las instrucciones AbortController 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

inserte la descripción de la imagen aquí

2.5.2 Cancelar solicitud de publicación

inserte la descripción de la imagen aquí

2.5.3 Iniciar una solicitud de nuevo después de la cancelación

inserte la descripción de la imagen aquí

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 getuna solicitud o postuna solicitud, se pasa una cancelTokenpropiedad .
  • Cuando queramos cancelar la llamada de la interfaz source, cancelsimplemente 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 tokendos cancelpropiedades.

/**
 * 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,
  }
}
  • tokenEs una instancia CancelTokendel (para obtener información específica, consulte CancelTokenel constructor);

  • cancelnew CancelTokenes executorel 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:

inserte la descripción de la imagen aquí

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 CancelTokenel prototipo :

  • throwIfRequested: arroja un error si el atributo de cancelación está presente.
  • subscribe: Almacene los parámetros entrantes listener;
  • unsubscribe: Eliminar los parámetros entrantes listener;

3.6 Uso compartido completo del código CancelToken

CancelTokenCombinació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.promisealmacene un Promiseobjeto y Promiseel estado del objeto será resolvePromisecontrolado .
  • Paso 5: tokenEl 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 Promsiecambia thenel método del objeto actual.

resolvePromiseCuando , solo se ejecutará el paso 6, pero se juzgará si el objeto devuelto por thenel método es Promsieun objeto, y si es Promsieun objeto se ejecutará el paso 6, de lo contrario, se omitirá.

3.7 solicitar xhr.js

Actualmente axiosadmite XMLHttpRequesty httpdos 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 XMLHttpRequestde . Si está presente cancelToken, active activamente config.cancelToken.subscribe(onCanceled)el método . Método para almacenar onCanceledcancelaciones CancelTokenen instancias de .

CancelTokenLa instancia, el almacenamiento onCanceledy 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()

inserte la descripción de la imagen aquí

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 cancelTokeny está en cancelTokenla instancia reasonse informará directamente de un error.

inserte la descripción de la imagen aquí

4. Resumen

En dos explicaciones:

4.1 uso

La forma de usarlo es pasar un cancelToken: CancelToken.source().tokentoken .

Se activa manualmente cuando es necesario cancelar la solicitudCancelToken.source().cancel

4.2 Principio

  1. CancelToken.source()devolverá un objeto source;

  2. sourceHay dos propiedades en el objeto: token,cancel;

{
    
    
  "source.token":"存储 CancelToken 的实例对象 A",
  "source.cancel":"存储可以改变 A.promise 状态的函数"
}
  1. A también almacena el método b para cancelar la solicitud actual;

  2. A.promiseCuando cambie el estado, se llamará al método b;

  3. Cuando queramos cancelar activamente la solicitud, llame source.cancel= "cambiar A.promiseestado = = 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 axiosestudiando 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:

  1. Comprender el uso de la solicitud axiosde cancelación ;
  2. Comprender la solicitud axiosde cancelación , las diferentes implementaciones de las dos versiones;

5.2 Mejoras específicas:

  1. En el código fuente, el estado de un Promiseobjeto está controlado por variables externas, que pueden ser imitadas;
  2. 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.
  3. En un CancelTokencó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

Supongo que te gusta

Origin blog.csdn.net/wswq2505655377/article/details/129202680
Recomendado
Clasificación