axios 中如何取消请求_从使用到原理_番茄出品

news2024/11/18 1:25:21

start

  • 最近频繁遇到一个问题,axios 是如何取消请求的?
  • 这篇文章将从新手小白的视角出发,从 axios 取消逻辑的基础使用,到原理分析,带你彻底了解并掌握 axios 中取消请求的“秘密”。
  • 编写时间:2023/02/24-23/14
  • 编写作者:lazy_tomato

1. 官方信息

1.1 源码地址

github-axios 点击跳转

1.2 axios 目前 npm 默认版本为 1.3.3

在这里插入图片描述

  • 我去年阅读 axios 源码的时候,默认版本还是 0.28.x ,今年变成了 1.x.x 了,这个需要注意下。

  • 我对 axios 版本号比较在意的原因是,看到过这句话:

    axios 的实现原理非常简单。
    但是 axios 的精髓在于它已经迭代了40个版本,但是大版本号使用为0. 
    npm 的 version 规则是首个版本号变化表示 api 不向下兼容。
    而 axios 增加了这么多功能。却始终保持没有 api 明显变化。
    这里 axios 内部使用了多种设计模式和架构模式。 (适配器,桥接,代理,抽象工厂,微内核设计。)
    
  • axios 整体源码不过千行,可模仿和学习的地方还是有很多的,互勉。

1.3 axios 中取消请求的api

在这里插入图片描述

由上述的截图可得,目前最新的 axios 的取消请求api,推荐使用 AbortController 。旧版本的 CancelToken v0.22.0 后弃用。

本文主要讲解 CancelToken , 有关 AbortController 的说明后续博客再做补充。

1.3.1 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.');

1.3.2 AbortController

const controller = new AbortController();

axios.get('/foo/bar', {
   signal: controller.signal
}).then(function(response) {
   //...
});
// cancel the request
controller.abort()

2. CancelToken 使用案例

官方文档中的演示案例不太直观,我们搭建一个服务,本地调试一下。

2.1安装依赖

npm init -y
 
npm i axios@0.22.0 express body-parser
# 注意,需要手动指定 axios 的版本,默认安装的axios是1.3.x版本的。

2.2 后端服务代码 main.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 启动本地服务

node ./main.js

2.4 前端代码 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 效果展示

2.5.1 取消get请求

在这里插入图片描述

2.5.2 取消post请求

在这里插入图片描述

2.5.3 取消后再次发起请求

在这里插入图片描述

2.6 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.');

对照官方的使用示例,总结一下:

  • 不管是 get 请求还是 post 请求,都是在调用接口的时候,传入一个 cancelToken 属性。
  • 当我们想要取消该接口调用的时候,调用 sourcecancel 方法即可。
  • 重复调用,接口会直接取消。

3. 对应源码

如果对源码不感兴趣,可直接跳跃到 4 ,查看结论即可。

3.1 CancelToken.source()

对照我们的使用案例,核心逻辑就是 CancelToken.source()。源代码如下:

CancelToken.source() 返回值其实就是一个对象,它包含 tokencancel 两个属性。

/**
 * 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 是构造函数 CancelToken 的实例(具体包含那些信息,可以查看 CancelToken 构造函数);

  • cancel 是在 new CancelToken 过程中,传入的 executor 函数接收到的形参;

阅读到这里,主要逻辑就在函数 CancelToken

3.2 CancelToken 整体概览

整体代码截图:

在这里插入图片描述

3.3 source 中的 cancel 属性存储的是什么?

查看 CancelToken 中的 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 Cancel

上方 new Cancel('Operation canceled by the user.') 对应源码

// 一个普通的函数,包含一个 message 属性
function Cancel(message) {
  this.message = message;
}

// 所以它返回的就是一个包含错误信息的对象
{ message: 'Operation canceled by the user.' }

3.5 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)
  }
}

这一小节,查看了 CancelToken 原型上的方法:

  • throwIfRequested:如果存在取消属性,抛出错误。
  • subscribe:存储传入的参数 listener
  • unsubscribe:删除传入的参数 listener

3.6 CancelToken 完整代码分享

CancelToken 全局逻辑梳理

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)
  })
}

难点说明:

  • 第 3,4 步:this.promise 上存储一个 Promise 对象,该 Promise 对象的状态将由外部的 resolvePromise 控制。
  • 第 5 步:当前实例对象的 token 属性,等于当前实例对象。
  • 第 6,7 步:这两部略微比较复杂。详细说明一下。

第 6 步定义的是 this.promise 成功之后触发的方法。

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 步定义的是 this.promise 成功时触发的方法。

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
}

第 6 步和第 7 步,如果顺序颠倒,执行结果完全不同。

源码的执行逻辑:第 6 步,先注册一个微任务,到队列。然后第 7 步更改当前 Promsie 对象的 then 方法。

resolvePromise 的时候,仅会执行第 6 步,但是会判断 then 方法返回的是否是 Promsie 对象,是 Promsie 对象则执行第 6 步,不是则跳过。

3.7 请求 xhr.js

目前的 axios 支持 XMLHttpRequesthttp 两种方式发送请求。

本文重点看一下 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)
  }
}

每发送一次请求,都会创建一个 XMLHttpRequest 的实例。如果存在 cancelToken ,主动触发 config.cancelToken.subscribe(onCanceled) 方法。存储取消的方法 onCanceledCancelToken 的实例上。

CancelToken 的实例,存储 onCanceled,这里利用的闭包的特性。

3.8 onCanceled

function onCanceled(cancel) {
  // 不存在 request 直接 return
  if (!request) {
    return
  }

  // 调用 取消的方法。
  request.abort()
  reject(cancel)
  // Clean up request
  request = null
}

取消请求,落实到具体实现,其实就是:request.abort(),也就是 XMLHttpRequest.abort()

MDN官方文档-XMLHttpRequest.abort()

在这里插入图片描述

3.9 取消一次请求,后续请求全部自动取消?

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;
  }
};

每次发起请求之前,在处理配置的时候就会触发校验函数: throwIfCancellationRequested

如果传入的参数存在 cancelToken,而且 cancelToken 实例上有 reason 则直接报错。

在这里插入图片描述

4. 总结

分两个讲解:

4.1 使用

使用的方式,就是在发送请求的时候,传入一个 cancelToken: CancelToken.source().token 令牌即可。

需要取消请求的时候,手动触发 CancelToken.source().cancel

4.2 原理

  1. CancelToken.source() 会返回一个对象 source

  2. source 对象上有两个属性分别为: token,cancel

{
  "source.token":"存储 CancelToken 的实例对象 A",
  "source.cancel":"存储可以改变 A.promise 状态的函数"
}
  1. A上还存储着,取消当前请求的方法 b;

  2. A.promise 状态改变,就会调用方法 b;

  3. 当我们想主动取消请求的时候,调用 source.cancel =》改变 A.promise 状态 =》调用方法 b;

**核心逻辑:**利用工厂函数,创建对象。利用闭包的特性,在对象中存储取消请求的方法,共外部调用。

end

axios相关博客

  • 使用axios传递参数,为什么new Date() 会变成 2022-08-10T11:33:56.849Z

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/370595.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Prometheus -- 浅谈Exporter

Prometheus系统 – Exporter原理 为什么我们需要Exporter&#xff1f; 广义上讲所有可以向Prometheus提供监控样本数据的程序都可以被称为一个Exporter。而Exporter的一个实例称为target&#xff0c;如下所示&#xff0c;Prometheus通过轮询的方式定期从这些target中获取样本…

你知道Java架构师学习路线该怎么走吗?你所缺少的是学习方法以及完整规划!

怎么成为一名Java架构师&#xff1f;都需要掌握哪些技术&#xff1f;Java架构师&#xff0c;首先要是一个高级Java攻城狮&#xff0c;熟练使用各种框架&#xff0c;并知道它们实现的原理。jvm虚拟机原理、调优&#xff0c;懂得jvm能让你写出性能更好的代码;池技术&#xff0c;什…

java 面向对象三大特性之多态 万字详解(超详细)

目录 前言 : 一、为什么需要多态 : 1.白璧微瑕 : 2.举栗&#xff08;请甘雨,刻晴,钟离吃饭&#xff09;: 3.代码 : 4.问题 : 二、什么是多态 : 1.定义 : 2.多态的实现步骤&#xff08;重要&#xff09; : 三、多态的使用 : 1.多态中成员方法的使用&#xff08;重要…

一块GPU搞定ChatGPT;ML系统入坑指南;理解GPU底层架构

1. 跑ChatGPT体量模型&#xff0c;从此只需一块GPU 在发展技术&#xff0c;让大模型掌握更多能力的同时&#xff0c;也有人在尝试降低AI所需的算力资源。最近&#xff0c;一种名为FlexGen的技术因为「一块RTX 3090跑ChatGPT体量模型」而获得了人们的关注。 虽然FlexGen加速后的…

Java 修饰符和多态

文章目录一、修饰符1. 权限修饰符2. 状态修饰符2.1 final2.2 static二、多态1. 成员访问特点2. 多态中的转型3. 多态案例一、修饰符 1. 权限修饰符 2. 状态修饰符 2.1 final final 关键字是最终的意思&#xff0c;可以修饰成员方法、成员变量及类。 //1.修饰成员变量 publi…

Git ---- IDEA 集成 Git

Git ---- IDEA 集成 Git1. 配置 Git 忽略文件2. 定位 Git 程序3. 初始化本地库4. 添加到暂存区5. 提交到本地库6. 切换版本7. 创建分支8. 切换分支9. 合并分支10. 解决冲突1. 配置 Git 忽略文件 1. Eclipse 特定文件 2. IDEA 特定文件 3. Maven 工程的 target 目录 问题1…

使用eNSP搭建基础IP网络 和 单交换机与VLAN分布实验(二层+三层)

Hello, 好久不见。上学期因为个人原因一直没有更新&#xff08;主要原因是上学期小小的摆了一下&#xff09;&#xff0c;这个学期我会继续在平台上分享我的学习经验。主要包括网络互联以及攻防的内容&#xff0c;也可能会更新深度学习相关的东西&#xff0c;主要就是看我到底有…

开源启智,筑梦未来!第四届OpenI/O启智开发者大会开幕

2023年2月24日&#xff0c;第四届OpenI/O启智开发者大会在深圳顺利开幕。本次活动由鹏城实验室、新一代人工智能产业技术创新战略联盟&#xff08;AITISA&#xff09;主办&#xff0c;OpenI启智社区、中关村视听产业技术创新联盟&#xff08;AVSA&#xff09;承办&#xff0c;华…

阿里 Java 程序员面试经验分享,附带个人学习笔记、路线大纲

背景经历 当时我工作近5年&#xff0c;明显感觉到了瓶颈期。说句不好听的成了老油条&#xff0c;可以每天舒服的混日子&#xff08;这也有好处&#xff0c;有时间准备面试&#xff09;。这对于个人成长不利&#xff0c;长此以往可能面临大龄失业。所以我觉得需要痛下决心改变一…

Spring Boot系列03--自动配置原理

目录1. 相关注解2. 自动配置原理分析3. 自动配置图示Spring Boot的核心优势&#xff1a;自动装配、约定大于配置。 1. 相关注解 ConfigurationProperties(prefix "前缀名")该注解用于自动配置的绑定&#xff0c;可以将application.properties配置中的值注入到 Bean…

加油站ai系统视频监测 yolov5

加油站ai系统视频监测通过yolov5网络模型深度学习边缘计算技术&#xff0c;加油站ai系统视频监测对现场卸油过程中人员违规离岗、现场灭火器没有按要求正确摆放、以及卸油前需要遵守静电释放15分钟、打电话、明火烟雾情况、抽烟行为进行自动识别。YOLO系列算法是一类典型的one-…

九龙证券|不惧美联储重回鹰派,这个板块强势领涨!游戏才刚刚开始?

美联储开释鹰派信号&#xff0c;商场再度堕入博弈美元反弹的预期之中。 美联储近日发布的2月议息会议纪要显现&#xff0c;上行通胀危险是影响美联储前景的要害因素&#xff0c;在通胀持续回落至2%之前&#xff0c;需求采取限制性方针。叠加欧元区1月份中心通胀升至历史最高纪录…

Spring MVC 源码- HandlerExceptionResolver 组件

HandlerExceptionResolver 组件HandlerExceptionResolver 组件&#xff0c;处理器异常解析器&#xff0c;将处理器&#xff08; handler &#xff09;执行时发生的异常&#xff08;也就是处理请求&#xff0c;执行方法的过程中&#xff09;解析&#xff08;转换&#xff09;成对…

Python学习-----模块5.0(文件管理大师-->os模块)

目录 前言&#xff1a; 1.os.getcwd() 2. os.listdir(path) 3.os.walk(path) 4.os.path.exists(path) 5.os.mkdir(path) 6.os.makedirs(path,exist_okTrue) 7.os.rmdir(path) 8.os.remove(path) 9.os.path.join(p1,p2) 10.os.path.split(path) 11.os.path.isdi…

【python】类的详解

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录PO verses OOPOOO当一个类很复杂的时候&#xff0c;考虑多弄一个类的改造私有类的模块化静态类verses动态类动态类查看模块源代码对象机制的基石 PyObjectPO verses OO PO PO耦合性高&#xff0c;很多过程…

手写Android性能监测工具,支持Fps/流量/内存/启动等

App性能如何量化:如何衡量一个APP性能好坏&#xff1f;直观感受就是&#xff1a;启动快、流畅、不闪退、耗电少等感官指标&#xff0c;反应到技术层面包装下就是&#xff1a;FPS&#xff08;帧率&#xff09;、界面渲染速度、Crash率、网络、CPU使用率、电量损耗速度等&#xf…

Linux命令之awk

awk是一个有强大的文本格式化能力的linux命令&#xff0c;早期是在Unix上实现的&#xff0c;linux后来也可以使用了&#xff0c;我们在Linux上使用的awk是gawk&#xff08;GNU awk的意思&#xff09; 语法 awk [option] 模式{动作} file option表示awk的可选参数&#xff0c;可…

mybatis与jpa

1、官方文档 mybatis&#xff1a;mybatis-spring – jpa&#xff1a;https://springdoc.cn/spring-data-jpa/ 应用文档 jpa详解_java菜鸟1的博客-CSDN博客 JPA简介及其使用详解_Tourist-xl的博客-CSDN博客_jpa的作用 2、使用比较 mybatis一般用于互联网性质的项目&#x…

zabbix4.0 Web页面配置 - 聚合图形的实现

目录 1、主机组Host groups配置 创建主机组 ​编辑 将一个主机添加至刚才创建的主机里面 2、用户参数UserParameter设置 示例&#xff1a; 添加一个参数&#xff1a;show.host.messages 模拟zabbix模板里面的参数再添加一个userparameter 3、触发器设置 示例&#xff1a; …

浏览器缓存之强缓存和协商缓存

为什么需要缓存? - 缓存的优点: 1.减少对服务器的访问次数,减轻了服务器的压力 2.节省用户网络带宽(就是省钱,带宽都是按流量算钱的) 3.从缓存读取更匀速减少等待优化了用户体验 - 缓存的缺点 资源被缓存后用户不能及时获取不到最新的资源,所以缓存不能乱用 强缓存 涉…