axios 请求中断和请求重试

1,596 阅读4分钟

前言

当用户访问我们的 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

请求中断

面试官:请求已经发出去了,如何取消掉这个已经发出去的请求?

面试者:(脑海里立马产生一个疑惑:已经发出去的请求还能取消掉?) 这个......这个......还真不知道,有什么用呀。

应用场景

  • 取消正在下载中的文件。
  • 点击不同的下拉框选项,向服务器发送新请求但携带不同的参数,响应回来的数据在折线图里面展示。每次请求的数据量较大,但其实只要渲染最后一次选择。
  • 应用场景迁移,一般在数据量较大的情况下,可通过请求中断,节省网络资源。

AbortController 请求控制器

后端

  • 初始化项目 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 插件可以更方便实现请求重试。

  1. 安装依赖 npm install axios-retry
  2. 配置 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 请求重试