当前位置: 首页 > news >正文

如何实现一个请求库?【面试场景题】

如何实现一个请求库?这个问题在面试中是一个常见的场景题,可以从第三方库的一些不足进行分析,然后答出如何实现这个库的步骤流程即可。

前端有诸多成熟的请求库,比如 axios、vue-request、swr 等,那么为什么在一些大中型项目中,放着成熟的请求库不用,仍然固执的选择重新造轮子?

  1. 定制化需求:成熟的请求库通常是通用型工具,难以完全满足特定业务场景的需求例如:
    • 统一处理错误码和异常
    • 自定义的请求拦截与响应拦截逻辑
    • 特定的请求缓存策略
    • 请求日志上报或埋点
  2. 统一接口规范
    • 对接后端统一的返回格式
    • 支持全局 Token 认证
    • 实现统一的超时控制、重试机制等
  3. 性能优化
    • 请求并发控制
    • 接口聚合(合并多个请求)
    • 预加载、懒加载策略

这些请求库到底有什么难以接受的缺陷,就连对它进行二次封装都难以解决问题?

  1. 扩展性受限
    • 虽然支持拦截器、插件等机制,但其内部结构可能不够灵活,无法很好地支持复杂的业务逻辑扩展
    • 某些定制化需求(如特定的缓存策略、重试机制)难以通过简单的封装实现
  2. 封装成本高
    • 二次封装往往只能“包裹”一层逻辑,而无法彻底控制底层行为(如重试策略、缓存机制等),导致封装后仍需频繁处理边界情况
    • 多层封装可能导致调用链路变长,调试困难,性能损耗增加
  3. 灵活性不足
    • 请求库的默认行为可能与项目需求冲突,例如默认的错误处理机制或超时控制策略,调整这些行为可能需要深入修改库的源码,而非简单的配置
  4. 维护复杂度高
    • 随着项目迭代,对请求库的定制化需求可能不断增加,依赖外部库的版本更新和兼容性问题会进一步增加维护成本

如果让你来实现一个完整的请求库,你会如何规划?

实现一个完整的请求库(HTTP 请求库)需要从功能完整性、易用性、性能和扩展性等多个方面进行规划

  1. 功能需求规划
    • 支持常见的 HTTP 方法:GET, POST, PUT, DELETE, PATCH 等
    • 支持设置请求头(Headers)
    • 支持发送请求体(Body),如 JSON、表单数据等格式
    • 支持查询参数(Query Parameters)
    • 支持响应拦截器与请求拦截器
    • 支持超时配置
    • 支持重试机制
  2. 模块化设计
    • RequestConfig:统一的请求配置接口,包含 URL、方法、headers、body 等
    • ResponseHandler:处理响应数据并返回标准化的响应对象
    • InterceptorManager:管理请求/响应拦截器,用于在请求前后执行逻辑(如添加 token、日志等)
  3. 异常处理
    • 请求超时
    • 网络错误
    • 响应状态码非 2xx(可自定义判定)
  4. 性能优化
    • 使用缓存策略
    • 支持并发控制(如 Promise.all + 控制并发数)
  5. 可扩展性设计
    • 插件系统:允许第三方开发者扩展功能(如自动重试插件、日志插件等)
  6. 测试
  7. 文档与示例
    • 提供详细的 API 文档
    • 提供常见使用场景的示例代码
    • 提供 TypeScript 类型定义文件

具体实现

基于 fetch 实现,实现了以下功能:

  1. 统一错误处理
    • 通过响应拦截器统一处理 HTTP 状态码
    • 支持自定义错误处理逻辑
  2. 请求/响应拦截器
    • 可以在请求发送前修改请求配置
    • 可以在响应返回后统一处理响应数据
    • 支持多个拦截器链式调用
  3. 请求缓存
    • 实现了基于内存的缓存系统
    • 支持设置缓存过期时间
    • 可以针对不同请求设置不同的缓存策略
  4. Token 认证
    • 支持全局设置认证 token
    • 自动将 token 添加到请求头
  5. 超时控制和重试机制
    • 可配置请求超时时间
    • 支持请求失败自动重试
    • 可设置最大重试次数
  6. 并发控制
    • 实现了请求并发数量限制
    • 使用队列管理超出并发限制的请求
  7. 请求聚合
    • 支持批量发送多个请求
    • 统一处理批量请求的结果
  8. 便捷方法
    • 提供了 get、post、put、delete 等常用方法
    • 支持自定义请求配置

RequestConfig 类

请求基础配置

// 请求配置类型定义
class RequestConfig {constructor(config = {}) {this.baseURL = config.baseURL || ''this.timeout = config.timeout || 5000this.headers = config.headers || {}this.maxRetries = config.maxRetries || 3 // 最大重试次数this.maxConcurrent = config.maxConcurrent || 5 // 最大并发数this.cacheTime = config.cacheTime || 0 // 缓存时间,0表示不缓存}
}

RequestInterceptor 类

// 请求拦截器
class RequestInterceptor {constructor() {this.interceptors = []}use(onFulfilled, onRejected) {this.interceptors.push({onFulfilled,onRejected})}execute(config) {// 使用 reduce 方法将每个拦截器串联起来,形成一个处理链,返回的是 configreturn this.interceptors.reduce((promise, interceptor) => {return promise.then(// 每个拦截器可以通过 onFulfilled 对配置进行处理并返回新的 config(config) => interceptor.onFulfilled(config),(error) => interceptor.onRejected(error))}, Promise.resolve(config))}
}

ResponseHandler 类

// 响应拦截器
class ResponseInterceptor {constructor() {this.interceptors = []}use(onFulfilled, onRejected) {this.interceptors.push({onFulfilled,onRejected})}execute(response) {return this.interceptors.reduce((promise, interceptor) => {return promise.then((response) => interceptor.onFulfilled(response),(error) => interceptor.onRejected(error))}, Promise.resolve(response))}
}

CacheManager 类

// 请求缓存管理
class CacheManager {constructor() {this.cache = new Map()}set(key, value, expireTime) {if (expireTime <= 0) returnconst item = {value,expire: Date.now() + expireTime}this.cache.set(key, item)}get(key) {const item = this.cache.get(key)if (!item) return nullif (Date.now() > item.expire) {this.cache.delete(key)return null}return item.value}clear() {this.cache.clear()}
}

ConcurrencyController 类

// 并发控制器
class ConcurrencyController {constructor(maxConcurrent) {this.maxConcurrent = maxConcurrentthis.currentConcurrent = 0this.queue = []}async add(fn) {if (this.currentConcurrent >= this.maxConcurrent) {await new Promise((resolve) => this.queue.push(resolve))}this.currentConcurrent++try {return await fn()} finally {this.currentConcurrent--if (this.queue.length > 0) {const next = this.queue.shift()next()}}}
}

HttpClient 类(核心)

// 主请求类
class HttpClient {constructor(config = new RequestConfig()) {this.config = configthis.requestInterceptor = new RequestInterceptor()this.responseInterceptor = new ResponseInterceptor()this.cacheManager = new CacheManager()this.concurrencyController = new ConcurrencyController(config.maxConcurrent)this.token = null// 添加默认响应拦截器处理错误码this.responseInterceptor.use((response) => {// ok 属性是 Fetch API 的 Response 对象的一个只读属性。它是一个布尔值,用来表示响应是否成功if (!response.ok) {throw new Error(`HTTP Error: ${response.status}`)}return response.json()},(error) => {throw error})}// 设置认证tokensetToken(token) {this.token = token}// 生成缓存keygenerateCacheKey(url, options) {return `${options.method || 'GET'}-${url}-${JSON.stringify(options.body || '')}`}// 执行请求async request(url, options = {}) {const cacheKey = this.generateCacheKey(url, options)const cachedResponse = this.cacheManager.get(cacheKey)if (cachedResponse) return cachedResponse// 重试计数let retries = 0const executeRequest = async () => {try {// 合并配置const finalOptions = {...options,headers: {...this.config.headers,...options.headers,...(this.token ? { Authorization: `Bearer ${this.token}` } : {})}}// 应用请求拦截器const processedConfig = await this.requestInterceptor.execute(finalOptions)// console.log('processedConfig', processedConfig);// 执行请求const response = await this.concurrencyController.add(() =>// race 接收一个 Promise 数组,一旦其中一个 Promise 被解决(resolve)或拒绝(reject),就立即以该 Promise 的结果作为自身结果来完成或拒绝。// 使用 Promise.race() 来实现超时处理,如果 fetch 在超时时间内完成,则返回结果;否则抛出异常Promise.race([fetch(this.config.baseURL + url, processedConfig),new Promise((_, reject) =>setTimeout(() => reject(new Error('Request timeout')),this.config.timeout))]))// 应用响应拦截器const processedResponse = await this.responseInterceptor.execute(response)// 缓存响应this.cacheManager.set(cacheKey,processedResponse,this.config.cacheTime)return processedResponse} catch (error) {if (retries < this.config.maxRetries) {retries++return executeRequest()}throw error}}return executeRequest()}// 批量请求async batch(requests) {return Promise.all(requests.map((req) => this.request(req.url, req.options)))}// 便捷方法get(url, options = {}) {return this.request(url, { ...options, method: 'GET' })}post(url, data, options = {}) {return this.request(url, {...options,method: 'POST',body: JSON.stringify(data),headers: {'Content-Type': 'application/json',...options.headers}})}put(url, data, options = {}) {return this.request(url, {...options,method: 'PUT',body: JSON.stringify(data),headers: {'Content-Type': 'application/json',...options.headers}})}delete(url, options = {}) {return this.request(url, { ...options, method: 'DELETE' })}
}// 导出实例
export default new HttpClient()

使用案例

import http from './fetch.js'// 配置全局token
http.setToken('your-auth-token')// 添加请求拦截器
http.requestInterceptor.use((config) => {config.headers['Custom-Header'] = 'value'// 在发送请求之前做些什么console.log('Request interceptor:', config)return config},(error) => {// 对请求错误做些什么console.error('Request error:', error)return Promise.reject(error)}
)// 添加响应拦截器
http.responseInterceptor.use((response) => {// 对响应数据做点什么console.log('Response interceptor:', response)return response},(error) => {// 对响应错误做点什么console.error('Response error:', error)return Promise.reject(error)}
)// 示例请求
async function testApi() {try {// 单个请求const data = await http.get('https://jsonplaceholder.typicode.com/posts')console.log('Single request result:', data)// 批量请求const batchResults = await http.batch([{ url: 'https://jsonplaceholder.typicode.com/posts/1' },{ url: 'https://jsonplaceholder.typicode.com/posts/2' }])console.log('Batch request results:', batchResults)// POST请求示例const postData = await http.post('https://jsonplaceholder.typicode.com/posts',{name: 'test',value: 123})console.log('POST request result:', postData)} catch (error) {console.error('API test error:', error)}
}// 运行测试
testApi()

文章转载自:
http://diaphony .riewr.cn
http://crossband .riewr.cn
http://ichthyosarcotoxism .riewr.cn
http://bifilar .riewr.cn
http://wormhole .riewr.cn
http://midiskirt .riewr.cn
http://dialectally .riewr.cn
http://polygenism .riewr.cn
http://pillowslip .riewr.cn
http://corporatism .riewr.cn
http://solute .riewr.cn
http://nccm .riewr.cn
http://ponderation .riewr.cn
http://saveloy .riewr.cn
http://ittf .riewr.cn
http://interfaith .riewr.cn
http://milling .riewr.cn
http://aluminium .riewr.cn
http://epistrophe .riewr.cn
http://italianise .riewr.cn
http://pachyrhizus .riewr.cn
http://kkk .riewr.cn
http://barque .riewr.cn
http://partwork .riewr.cn
http://ritualization .riewr.cn
http://multicoloured .riewr.cn
http://lacrimator .riewr.cn
http://fourfold .riewr.cn
http://gymnoplast .riewr.cn
http://accelerative .riewr.cn
http://www.dtcms.com/a/224591.html

相关文章:

  • 牛客小白月赛117
  • 实施ESOP投入收益研究报告
  • 趋势直线指标
  • C语言学习——C语言强制类型转换2023.12.20
  • 【Java学习笔记】内部类(重点)
  • 最小二乘准则例题
  • 22睿抗省赛真题
  • 电脑重装或者开机出现错误
  • 【Oracle】TCL语言
  • Maestro CLI云端测试以及github cl,bitrise原生cl的测试流程
  • 截面动量策略思路
  • javaweb-maven以及http协议
  • 【Linux系列】Linux/Unix 系统中的 CPU 使用率
  • 【数据治理】要点整理-信息技术数据质量评价指标-GB/T36344-2018
  • 【shell】让 CPU 运行到满负荷状态
  • 家用和类似用途电器的安全 第1部分:通用要求 与2005版差异(7)
  • Vue 3 中ref 结合ts 获取 DOM 元素的实践指南。
  • 数据结构:时间复杂度(Time Complexity)和空间复杂度(Space Complexity)
  • 131. 分割回文串-两种回溯思路
  • 命令行式本地与服务器互传文件
  • 5G-A:开启通信与行业变革的新时代
  • Jmeter requests
  • 通过mqtt 发布温湿度
  • hot100 -- 1.哈希系列
  • AI炼丹日志-26 - crawl4ai 专为 AI 打造的爬虫爬取库 上手指南
  • 第三方软件评测机构如何助力软件品质提升及企业发展?
  • Baklib知识中台驱动服务升级
  • Java基础 Day26
  • android 媒体框架之MediaCodec
  • leetcode hot100刷题日记——31.二叉树的直径