前言
当用户访问我们的 Web 应用程序时,HTTP 请求可能会由于网络不稳定而失败,例如超时或网络异常。
在这种情况下,通常会有一个异常页面通知用户他们可以稍后重新进入 Web 应用程序。如果我们重试这些失败的 HTTP 请求,用户可能不需要退出并重新进入我们的 Web 应用程序,体验会更好。
我们将首先了解 Axios 请求失败时可能发生的所有异常,然后使用 Axios 的拦截器重试 HTTP 请求。
演示环境
- 电脑 - Windows 10
- Node - v16.15.0
- Npm - 9.4.0
- Express - 4.18.2
- Vue 3.3.4
- axios 1.4.0
请求中断
面试官:请求已经发出去了,如何取消掉这个已经发出去的请求?
面试者:(脑海里立马产生一个疑惑:已经发出去的请求还能取消掉?) 这个......这个......还真不知道,有什么用呀。
应用场景
- 取消正在下载中的文件。
- 点击不同的下拉框选项,向服务器发送新请求但携带不同的参数,响应回来的数据在折线图里面展示。每次请求的数据量较大,但其实只要渲染最后一次选择。
- 应用场景迁移,一般在数据量较大的情况下,可通过请求中断,节省网络资源。
后端
- 初始化项目
npm init
- 安装依赖
pnpm i express cors
- 新建 files 目录,并准备一个 video.mp4 文件
- 新建
app.js
文件,参考以下代码,返回视频文件
js
const express = require('express')
const cors = require('cors')
const path = require('path')
const fs = require('fs')
const app = express()
app.use(cors())
// 主机,端口
const host = 'http://localhost'
const port = 3000
app.get('/video', (req, res) => {
// 获取文件路径
const filePath = path.join(__dirname, 'files', 'video.mp4')
// 获取文件信息
const stat = fs.statSync(filePath)
// 获取文件大小
const fileSize = stat.size
// 设置响应头
res.setHeader('Content-Length', fileSize)
res.setHeader('Content-Type', 'video/mp4')
// 创建可读流并将其管道传输到响应流中
fs.createReadStream(filePath).pipe(res)
})
// 启动服务器
app.listen(port, () => {
console.log(`服务器启动成功 ${host}:${port}`)
})
前端
- 通过 AbortController 中断请求
vue
<script setup lang="ts">
import axios from 'axios'
import { ref } from 'vue'
const progress = ref(0) // 进度条百分比
let controller: AbortController // 中止控制器
// 中止下载
const abortDownload = () => {
if (controller) {
controller.abort() // 使用 abort 方法中止下载
console.log('中止下载')
}
}
// 下载视频
const fetchVideo = () => {
controller = new AbortController() // 创建 AbortController
axios({
// 将中止控制器传递给 axios 的 get 方法
method: 'GET',
url: 'http://localhost:3000/video',
signal: controller.signal,
responseType: 'arraybuffer',
onDownloadProgress: (progressEvent) => {
// 计算进度百分比
progress.value = Math.round((progressEvent.loaded / progressEvent.total!) * 100)
}
})
.then((response) => {
console.log('下载完成', response)
// ✅ 保存下载的文件
const { buffer } = new Uint8Array(response.data)
const blob = new Blob([buffer], { type: 'application/octet-stream' })
const link = document.createElement('a') // 创建链接元素
link.href = URL.createObjectURL(blob) // 将 Blob 对象转换为 URL
link.download = 'video.mp4' // 设置文件名
link.click() // 模拟点击链接元素
})
.catch((err) => {
if (axios.isCancel(err)) {
console.log('下载被取消')
} else if (err.name === 'AbortError') {
console.log('下载被中止')
} else {
console.error(`下载错误:${err.message}`)
}
})
}
</script>
<template>
<div>
<button class="download" @click="fetchVideo">下载视频</button>
<button class="abort" @click="abortDownload">中止下载</button>
<div class="progress-bar">
<div class="progress" :style="{ width: progress + '%' }"></div>
{{ progress }}%
</div>
</div>
</template>
<style scoped>
.progress-bar {
height: 20px;
background-color: #eee;
margin-top: 10px;
}
.progress {
width: 0%;
height: 100%;
background-color: #4caf50;
transition: width 0.2s linear;
}
</style>
请求重试
当用户访问我们的 Web 应用程序时,HTTP 请求可能会由于网络不稳定而失败,例如超时或网络异常。
后端
- 请求 3s 后返回
js
const express = require('express')
const cors = require('cors')
const app = express()
app.use(cors())
// 主机,端口
const host = 'http://localhost'
const port = 3000
app.get('/delay_3s_data', (req, res) => {
// 延迟响应
setTimeout(() => {
res.send({ code: 0, msg: '请求成功', data: '这是延迟 3 秒返回的数据' })
}, 3000)
})
// 启动服务器
app.listen(port, () => {
console.log(`服务器启动成功 ${host}:${port}`)
})
前端
- 添加自定义配置
retries
和retryDelay
- 响应拦截器实现请求重试
vue
<script setup lang="ts">
import axios from 'axios'
const request = axios.create({
baseURL: 'http://localhost:3000',
// 设置请求超时时间为5秒
timeout: 2000,
retries: 3, // 设置重试次数为3次
retryDelay: 1000, // 设置重试的间隔时间
} as any)
// 添加响应拦截器
request.interceptors.response.use(
(response) => {
// 对响应数据做些什么
return Promise.resolve(response.data)
},
(error) => {
const config = error.config
// 如果config不存在或未设置重试选项,则拒绝
if (!config || !config.retries) {
return Promise.reject(error)
}
// 设置变量来跟踪重试次数
config.__retryCount = config.__retryCount || 0
// 检查是否达到最大重试次数
if (config.__retryCount >= config.retries) {
return Promise.reject(error)
}
// 增加重试计数器
config.__retryCount += 1
// 创建一个新的Promise来处理每次重试之前等待一段时间
const backoff = new Promise((resolve) => {
setTimeout(() => {
resolve('重新请求:' + config.__retryCount)
}, config.retryDelay || 1)
})
// 返回Promise,以便Axios知道我们已经处理了错误
return backoff.then((txt) => {
console.log(txt)
return request(config)
})
},
)
// 请求中止控制器
let controller: AbortController
// --- 获取数据 ---
const getData = async () => {
controller = new AbortController()
const res = await request({
signal: controller.signal, // 添加请求中止标识
method: 'GET',
url: '/delay_3s_data',
})
console.log('成功获取数据', res)
}
const stop = () => {
// 中止网络请求
controller.abort()
}
</script>
<template>
<h1>axios请求重试</h1>
<button @click="getData()">发送请求</button>
<button @click="stop()">中止请求</button>
</template>
axios-retry 插件
axios-retry 1.6k 插件可以更方便实现请求重试。
- 安装依赖
npm install axios-retry
- 配置 axios-retry 核心属性
vue
<script setup lang="ts">
import axios from 'axios'
import axiosRetry from 'axios-retry'
const request = axios.create({
baseURL: 'http://localhost:3000',
timeout: 2000,
})
// axios-retry 插件
axiosRetry(request, {
retries: 3, // 设置重试次数
retryDelay: () => 500, // 设置重试延迟时间
shouldResetTimeout: true, // 重置请求超时时间
retryCondition: (error) => ['ECONNABORTED', 'ERR_NETWORK'].includes(error.code!), // 重试条件
})
// 请求中止控制器
let controller: AbortController
// --- 获取数据 ---
const getData = async () => {
// 请求控制器
controller = new AbortController()
const res = await request({
method: 'GET',
url: '/delay_3s_data',
signal: controller.signal, // 添加请求中止标识
})
console.log('成功获取数据', res)
}
const stop = () => {
// 中止网络请求
controller.abort()
}
</script>
<template>
<h1>axios请求重试-axiosRetry</h1>
<button @click="getData()">发送请求</button>
<button @click="stop()">中止请求</button>
</template>
总结
数据量较大的情况下,可通过请求中断,节省网络资源。
由于网络不稳定而失败,例如 Network timeout 或 Network error。
使用 Axios 的拦截器拦截响应,则尝试再次发送请求,通过设置 retry 和 retryDelay 来控制重试请求的数量和每个请求之间的间隔。
- 请求中断
- 请求重试实现原理
- axios-retry 请求重试