前端接口失败重试怎么做才不卡死页面?

设计师宁馨 阅读 10

我在 Vue 项目里加了个接口失败自动重试的逻辑,但有时候网络差,连续重试好几次,页面就卡住了,用户操作都没反应。是不是我的重试方式有问题?

我试过用 setTimeout 延迟重试,也加了最大重试次数限制,但还是偶尔卡顿。比如下面这段代码:

<script setup>
import { ref } from 'vue'
import axios from 'axios'

const retryRequest = async (url, retries = 3) => {
  try {
    return await axios.get(url)
  } catch (error) {
    if (retries > 0) {
      await new Promise(r => setTimeout(r, 1000))
      return retryRequest(url, retries - 1)
    }
    throw error
  }
}
</script>

这种写法在快速失败时会不会阻塞主线程?有没有更轻量、不卡 UI 的重试方案?

我来解答 赞 0 收藏
二维码
手机扫码查看
1 条解答
シ爱景
シ爱景 Lv1
你的代码本身是 async/await 写的,理论上不会阻塞主线程。问题可能出在调用方式和缺少一些保护机制。

快速失败时最常见的问题是:请求立即失败然后立即重试,短时间内发了一堆请求,浏览器资源被吃满了。固定 1 秒延迟在网络抖动时也不够科学。

给你一个优化后的写法:

const retryRequest = async (url, options = {}) => {
const {
maxRetries = 3,
baseDelay = 1000,
maxDelay = 10000
} = options

let lastError

for (let i = 0; i <= maxRetries; i++) {
try {
const response = await axios.get(url, {
timeout: 5000 // 加个超时,别让请求一直挂着
})
return response
} catch (error) {
lastError = error

// 最后一次重试失败就直接抛错
if (i < maxRetries) {
// 指数退避:1s -> 2s -> 4s,避免连续炸请求
const delay = Math.min(baseDelay * Math.pow(2, i), maxDelay)
await new Promise(r => setTimeout(r, delay))
}
}
}

throw lastError
}


几个关键点:

第一,用 for 循环代替递归,更清晰也好控制。指数退避很重要,网络差的时候别急着连续重试,等久一点。

第二,axios 记得配 timeout。快速失败往往是请求发出去但一直 pending,加个 5 秒左右超时能让它早点进入 catch 触发重试逻辑。

第三,如果你在 Vue 里用,建议把请求挂到组件生命周期上,用 abortController 取消重复请求:

import { ref, onUnmounted } from 'vue'

const controller = ref(null)

const fetchData = async () => {
// 取消之前的请求
if (controller.value) {
controller.value.abort()
}
controller.value = new AbortController()

try {
const res = await axios.get('/api/xxx', {
signal: controller.value.signal
})
// 处理结果
} catch (e) {
if (axios.isCancel(e)) return // 忽略取消错误
// 重试逻辑
}
}

onUnmounted(() => {
controller.value?.abort()
})


这样用户疯狂点击或者快速切换页面时,不会堆一堆请求。

还有个偏门但管用的招:如果你的重试逻辑特别重,可以把整个请求包装成 Promise 丢到 setTimeout(0) 里,下一帧再执行,给 UI 一个喘气的机会。不过正常情况下上面那些优化就够了。

注意安全的是,别在重试里带敏感参数重复发,有些接口重复调用会出问题。还有如果涉及支付类操作,建议别自动重试,让用户手动确认。
点赞
2026-03-13 18:23