共计 3152 个字符,预计需要花费 8 分钟才能阅读完成。
背景痛点:默认适配器的局限性
axios 的默认适配器(Adapter)虽然能覆盖大部分浏览器和 Node.js 环境,但在特定场景下会暴露明显短板:

- SSR(Server-Side Rendering)场景 :浏览器端依赖的
XMLHttpRequest在服务端不可用,需强制切换至 Node.js 的http模块 - 混合协议需求 :默认适配器无法处理
file://或自定义协议(如electron://)的请求 - 环境隔离要求:部分小程序容器禁用了标准 Web API,需要兼容其内置网络模块
技术对比:底层实现的差异
不同底层实现的特性直接影响适配器选择(性能数据基于 Chrome 102 基准测试):
| 实现方式 | 是否支持 Stream | 是否支持 Progress | 内存开销 | 典型使用场景 |
|---|---|---|---|---|
| XMLHttpRequest | ❌ | ✅ | 较高 | 传统浏览器环境 |
| Fetch API | ✅ | ❌ | 较低 | 现代浏览器 /PWA |
| Node.js http 模块 | ✅ | ✅ | 中等 | 服务端环境 |
核心实现:适配器调用链解析
调用链示意图
sequenceDiagram
Client->>+Axios: request(config)
Axios->>+dispatchRequest: 转换配置
dispatchRequest->>+adapter: 调用适配器
adapter-->>-dispatchRequest: 返回响应
dispatchRequest-->>-Axios: 转换响应
Axios-->>-Client: 最终结果
自定义适配器实现示例
/**
* 带重试机制的自定义适配器
* @param config 请求配置
* @returns Promise<AxiosResponse>
*/
const retryAdapter = async (config: AxiosRequestConfig): Promise<AxiosResponse> => {const { retryTimes = 3, retryDelay = 1000} = config
for (let attempt = 1; attempt <= retryTimes; attempt++) {
try {
// 实际请求逻辑(示例使用 fetch)const response = await fetch(config.url!, {
method: config.method,
headers: config.headers,
body: config.data
})
return {data: await response.json(),
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries()),
config
}
} catch (error) {if (attempt >= retryTimes) throw error
await new Promise(resolve => setTimeout(resolve, retryDelay * attempt))
}
}
throw new Error('Unreachable code')
}
生产环境建议
内存泄漏防范
- 使用
CancelToken或AbortController取消不再需要的请求 - 在 React 组件卸载时清理未完成请求:
useEffect(() => {const source = axios.CancelToken.source() axios.get('/api', { cancelToken: source.token}) .catch(err => {if (!axios.isCancel(err)) {// 处理真实错误} }) return () => source.cancel('Component unmounted') }, [])
并发控制
通过 p-map 等库实现队列控制:
import pMap from 'p-map'
const urls = ['/api1', '/api2', '/api3']
const results = await pMap(
urls,
url => axios.get(url),
{concurrency: 2} // 最大并发数
)
错误分层处理
建立错误分类机制:
try {await axios.get('/api')
} catch (err) {if (err.response) {
// 业务错误(4xx/5xx)console.error('Business error:', err.response.status)
} else if (err.request) {
// 网络错误(无响应)console.error('Network error:', err.message)
} else {
// 代码错误
console.error('Setup error:', err.message)
}
}
代码规范与测试
类型安全实现
interface CustomAdapterConfig extends AxiosRequestConfig {
adapterRetry?: {
times: number
delay: number
}
}
// 通过模块增强扩展类型声明
declare module 'axios' {
interface AxiosRequestConfig {
adapterRetry?: {
times: number
delay: number
}
}
}
单元测试示例
jest.mock('./adapter', () => ({customAdapter: jest.fn()
.mockRejectedValueOnce(new Error('First fail'))
.mockResolvedValue({data: 'success'})
}))
test('should retry on failure', async () => {
const res = await axios.get('/test', {
adapter: customAdapter,
adapterRetry: {times: 2, delay: 100}
})
expect(res.data).toBe('success')
expect(customAdapter).toHaveBeenCalledTimes(2)
})
延伸思考
动态适配器切换
通过环境检测自动选择适配器:
const dynamicAdapter = (config: AxiosRequestConfig) => {if (typeof XMLHttpRequest !== 'undefined') {return xhrAdapter(config) // 浏览器环境
} else if (typeof process !== 'undefined') {return httpAdapter(config) // Node 环境
} else {return customAdapter(config) // 小程序等特殊环境
}
}
WebSocket 适配器示例
const wsAdapter = (config: AxiosRequestConfig) => {return new Promise((resolve, reject) => {const ws = new WebSocket(config.url!.replace(/^http/, 'ws'))
ws.onmessage = (event) => {
resolve({data: JSON.parse(event.data),
status: 200,
statusText: 'OK',
headers: {},
config
})
}
ws.onerror = (error) => {reject(error)
}
})
}
总结
通过自定义适配器,我们可以突破 axios 的默认限制,实现:
- 环境无关的请求处理
- 特殊协议的支持扩展
- 细粒度的性能优化控制
建议在实际项目中根据具体需求选择合适的适配策略,并通过单元测试保证不同环境下的行为一致性。
正文完
