React Native开关飞行模式后Axios请求长时间无响应问题咨询
问题根因
- 全局固定
cancelToken配置错误:创建Axios实例时绑定了静态单例cancel token,所有请求共用同一个token上下文,断网期间堆积的请求无法被独立回收,会持续占用请求通道。 - Axios原生timeout存在生效盲区:该超时计时仅在TCP连接正常建立后才会启动,断网状态下连接处于半开、SYN重传、DNS缓存失效的阶段时,timeout不会触发,会一直等待系统网络栈返回结果。
- 移动端系统网络栈不会自动清理断网期间堆积的失效连接:重连后旧的失效连接会占满连接池配额,新发起的请求只能排队等待,必须等旧请求全部超时(系统层TCP超时默认可能长达几十秒到数分钟)才会释放通道,这也是强杀应用才能恢复的核心原因——强杀会直接清空整个连接池和pending请求队列。
修复方案
1. 修正cancelToken的错误用法
禁止在Axios实例级别绑定固定的全局cancelToken,否则任意一个请求触发取消都会导致所有绑定该token的请求被中断,这也是当前配置里的明显bug。移除实例级别的固定cancelToken配置,改为每个请求独立生成cancelToken,避免单token失效拖垮所有请求:
// 修正后的Axios实例配置,移除全局固定cancelToken export const axiosInstance = api.create({ baseURL: BASE_URL, timeout: 10000, headers: { source: API_SOURCE, apiVersion: API_VERSION, 'Content-Type': 'application/json', }, })
2. 监听网络状态,重连时主动清理堆积请求
通过网络状态监听,在离线切回在线的节点主动清空所有pending状态的旧请求,避免失效请求占用连接池:
let isOffline = false const pendingRequests = new Map() // 请求拦截器:为每个请求绑定独立cancelToken,记录pending队列 axiosInstance.interceptors.request.use(config => { const source = axios.CancelToken.source() config.cancelToken = source.token pendingRequests.set(`${config.method}_${config.url}`, source) // 重连后增加短延迟,等系统网络栈完成初始化再发请求 if (isOffline) { return new Promise(resolve => setTimeout(() => { config.headers['Connection'] = 'close' // 禁用持久连接复用,避免拿到失效连接 resolve(config) }, 200)) } config.headers['Connection'] = 'close' return config }) // 响应拦截器:请求结束后从pending队列移除 axiosInstance.interceptors.response.use( response => { pendingRequests.delete(`${response.config.method}_${response.config.url}`) return response }, error => { if (error.config) { pendingRequests.delete(`${error.config.method}_${error.config.url}`) } return Promise.reject(error) } ) // 绑定网络状态监听 window.addEventListener('offline', () => { isOffline = true }) window.addEventListener('online', () => { // 重连瞬间取消所有堆积的旧请求 pendingRequests.forEach(source => source.cancel('网络重连,取消过期请求')) pendingRequests.clear() isOffline = false })
3. 增加双重超时兜底,覆盖Axios timeout盲区
用Promise.race实现从请求发起瞬间就开始计时的兜底超时,解决连接建立阶段timeout不生效的问题:
/** * 带全链路超时兜底的请求包装 * @param {Promise} requestPromise 原始请求Promise * @param {number} timeout 超时时长,默认10s */ const withTimeout = (requestPromise, timeout = 10000) => { return Promise.race([ requestPromise, new Promise((_, reject) => { setTimeout(() => reject(new Error('REQUEST_TIMEOUT')), timeout) }) ]) } // 业务请求改造示例 async testApiCall(params: { status: boolean; set?: boolean }, state) { try { const { status, set = true } = params console.log('before call') const result = (await withTimeout(setStatus({ online: status }))) as any console.log('after call', result) return result } catch (error) { // 被取消/超时的请求可自动重试1次,避免偶发失败 if (axios.isCancel(error) || error.message === 'REQUEST_TIMEOUT') { return withTimeout(setStatus({ online: status })) } return error } }
4. 混合开发场景额外优化
如果是App内嵌WebView场景,可协调原生端在网络重连、飞行模式切换时,主动清空WebView网络缓存、重置网络连接池,从底层规避失效连接残留问题。
内容的提问来源于stack exchange,提问作者Riku
相关产品推荐
相关产品推荐

