XHR / Fetch / Axios 请求的取消请求与请求重试

XHR / Fetch / Axios 请求的取消请求请求重试是前端性能优化与稳定性处理的重点,也是面试高频内容。下面是这三种方式的详解封装方案(可直接复用)。


✅ 一、Axios 取消请求与请求重试封装

1. 安装依赖(可选,用于扩展)

bash 复制代码
npm install axios

2. 封装 cancelToken 与 retry 的 axios 请求模块

js 复制代码
// axiosRequest.js
import axios from 'axios'

const pendingMap = new Map()

// 生成唯一 key(用于标记请求)
function getRequestKey(config) {
  const { method, url, params, data } = config
  return [method, url, JSON.stringify(params), JSON.stringify(data)].join('&')
}

// 添加请求到 pendingMap
function addPending(config) {
  const key = getRequestKey(config)
  config.cancelToken = new axios.CancelToken(cancel => {
    if (!pendingMap.has(key)) {
      pendingMap.set(key, cancel)
    }
  })
}

// 移除请求
function removePending(config) {
  const key = getRequestKey(config)
  if (pendingMap.has(key)) {
    const cancel = pendingMap.get(key)
    cancel && cancel()
    pendingMap.delete(key)
  }
}

// 重试机制封装
function retryAdapterEnhancer(adapter, options = {}) {
  const { retries = 3, delay = 1000 } = options

  return async config => {
    let retryCount = 0
    const request = async () => {
      try {
        return await adapter(config)
      } catch (err) {
        if (retryCount < retries) {
          retryCount++
          await new Promise(res => setTimeout(res, delay))
          return request()
        } else {
          return Promise.reject(err)
        }
      }
    }
    return request()
  }
}

// 创建实例
const axiosInstance = axios.create({
  timeout: 5000,
  adapter: retryAdapterEnhancer(axios.defaults.adapter, { retries: 2, delay: 1000 })
})

// 请求拦截器
axiosInstance.interceptors.request.use(config => {
  removePending(config)
  addPending(config)
  return config
})

// 响应拦截器
axiosInstance.interceptors.response.use(
  response => {
    removePending(response.config)
    return response
  },
  error => {
    if (axios.isCancel(error)) {
      console.warn('Request canceled:', error.message)
    }
    return Promise.reject(error)
  }
)

export default axiosInstance

✅ 使用示例:

js 复制代码
import request from './axiosRequest'

request.get('/api/data', { params: { id: 1 } })
  .then(res => console.log(res))
  .catch(err => console.error(err))

✅ 二、Fetch 封装(支持取消请求 & 重试)

1. 使用 AbortController 封装取消与重试

js 复制代码
// fetchRequest.js
export function fetchWithRetry(url, options = {}, retries = 3, delay = 1000) {
  const controller = new AbortController()
  options.signal = controller.signal

  const fetchData = (retryCount = 0) => {
    return fetch(url, options).then(res => {
      if (!res.ok) throw new Error('Network response was not ok')
      return res
    }).catch(err => {
      if (retryCount < retries && err.name !== 'AbortError') {
        return new Promise(resolve =>
          setTimeout(() => resolve(fetchData(retryCount + 1)), delay)
        )
      }
      throw err
    })
  }

  return {
    promise: fetchData(),
    cancel: () => controller.abort()
  }
}

✅ 使用示例:

js 复制代码
const { promise, cancel } = fetchWithRetry('/api/data', {}, 2, 1000)

promise
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.error(err))

// 调用 cancel() 可随时取消请求

✅ 三、XHR 原生封装(带取消 & 重试)

js 复制代码
// xhrRequest.js
export function xhrWithRetry({ url, method = 'GET', data = null, retries = 3, delay = 1000 }) {
  let xhr = null

  const send = (retryCount = 0, resolve, reject) => {
    xhr = new XMLHttpRequest()
    xhr.open(method, url, true)
    xhr.onreadystatechange = () => {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300) {
          resolve(xhr.responseText)
        } else if (retryCount < retries) {
          setTimeout(() => send(retryCount + 1, resolve, reject), delay)
        } else {
          reject(new Error(`Request failed: ${xhr.status}`))
        }
      }
    }
    xhr.onerror = () => reject(new Error('Network Error'))
    xhr.send(data)
  }

  const promise = new Promise((resolve, reject) => send(0, resolve, reject))
  return {
    promise,
    cancel: () => xhr && xhr.abort()
  }
}

✅ 使用示例:

js 复制代码
const { promise, cancel } = xhrWithRetry({ url: '/api/data' })

promise.then(res => console.log(res)).catch(err => console.error(err))

// 可随时调用取消
cancel()

✅ 总结对比

特性/方式 取消支持 重试支持 易用性 推荐场景
Axios ✔️ CancelToken ✔️ 自定义 adapter ✅ 最佳 Vue/React 项目
Fetch ✔️ AbortController ✔️ 手动封装 ✅ 清晰 原生 / SSR 项目
XHR ✔️ abort() ✔️ 手动封装 ❌ 复杂 老旧兼容需求

如果你想把这三套方案整合成一个通用库或 TypeScript 模块,我也可以帮你封装成统一接口版本。是否需要我继续处理这部分?

相关推荐
我是小路路呀34 分钟前
element级联选择器:已选中一个二级节点,随后又点击了一个一级节点(仅浏览,未确认选择),此时下拉框失去焦点并关闭
javascript·vue.js·elementui
程序员爱钓鱼40 分钟前
Node.js 编程实战:文件读写操作
前端·后端·node.js
Allen正心正念202542 分钟前
网络编程与通讯协议综合解析
网络
PineappleCoder1 小时前
工程化必备!SVG 雪碧图的最佳实践:ID 引用 + 缓存友好,无需手动算坐标
前端·性能优化
bing_feilong1 小时前
ubuntu中的WIFI与自身热点切换
网络
CodeByV1 小时前
【网络】UDP 协议深度解析:从五元组标识到缓冲区
网络·网络协议·udp
JIngJaneIL1 小时前
基于springboot + vue古城景区管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
敲敲了个代码2 小时前
隐式类型转换:哈基米 == 猫 ? true :false
开发语言·前端·javascript·学习·面试·web
澄江静如练_2 小时前
列表渲染(v-for)
前端·javascript·vue.js
虹科网络安全2 小时前
艾体宝洞察 | 利用“隐形字符”的钓鱼邮件:传统防御为何失效,AI安全意识培训如何补上最后一道防线
运维·网络·安全