axios适配器函数调用深度解析:从原理到最佳实践

1次阅读
没有评论

共计 3152 个字符,预计需要花费 8 分钟才能阅读完成。

image.webp

背景痛点:默认适配器的局限性

axios 的默认适配器(Adapter)虽然能覆盖大部分浏览器和 Node.js 环境,但在特定场景下会暴露明显短板:

axios 适配器函数调用深度解析:从原理到最佳实践

  • 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')
}

生产环境建议

内存泄漏防范

  1. 使用 CancelTokenAbortController取消不再需要的请求
  2. 在 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 的默认限制,实现:

  • 环境无关的请求处理
  • 特殊协议的支持扩展
  • 细粒度的性能优化控制

建议在实际项目中根据具体需求选择合适的适配策略,并通过单元测试保证不同环境下的行为一致性。

正文完
 0
评论(没有评论)