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

HarmonyOS 5.0实战:基于Promise与拦截器构建企业级网络请求库

1. 为什么企业级应用需要更强大的网络请求库

在开发一个中型电商App时,网络请求就像App的血管系统,负责在各个功能模块之间输送数据。你可能已经用过简单的HTTP请求封装,但当用户量达到十万级别时,问题就开始显现了:某个促销活动导致服务器压力激增,部分请求超时;用户长时间使用后token过期需要自动刷新;某些关键接口需要记录完整的请求日志用于排查问题...

我去年参与开发的一个跨境电商项目就遇到过这样的困境。最初我们使用简单的请求封装,随着业务复杂度提升,代码里到处都是重复的错误处理逻辑。后来花了三周时间重构网络层,才让系统稳定下来。这段经历让我深刻认识到:一个好的网络请求库应该是可配置、可扩展、易维护的

Promise在HarmonyOS中的实现非常成熟,它解决了传统回调地狱的问题。但仅仅使用Promise还不够,我们还需要:

  • 拦截器机制:在请求发出前和响应返回后插入处理逻辑
  • 错误统一处理:避免在每个请求里重复写错误提示
  • 请求队列管理:防止高并发场景下的资源竞争
  • 自动重试策略:对特定类型的失败请求进行智能重试

2. 构建基础Promise请求框架

2.1 设计核心接口

我们先从基础结构开始,定义三个核心类型:

// 响应数据结构 interface ResponseData<T> { code: number message: string data: T timestamp?: number } // 请求配置项 interface RequestConfig { url: string method?: HttpMethod // GET/POST/PUT等 params?: object // URL参数 data?: object // 请求体数据 headers?: Record<string, string> timeout?: number // 单独设置超时 retry?: number // 重试次数 } // 拦截器类型 interface Interceptor { onRequest?: (config: RequestConfig) => RequestConfig | Promise<RequestConfig> onResponse?: <T>(response: ResponseData<T>) => ResponseData<T> | Promise<ResponseData<T>> onError?: (error: Error) => Promise<never> }

这个设计比基础封装更完善的地方在于:

  1. 明确区分了URL参数(params)和请求体数据(data)
  2. 每个请求可以单独配置超时和重试策略
  3. 预留了扩展字段如timestamp

2.2 实现基础请求方法

让我们实现一个带Promise的基础请求方法:

class HttpRequest { private baseURL: string private defaultConfig: RequestConfig = { timeout: 10000, retry: 0 } constructor(baseURL: string) { this.baseURL = baseURL } async request<T>(config: RequestConfig): Promise<ResponseData<T>> { const mergedConfig = { ...this.defaultConfig, ...config } const httpRequest = http.createHttp() try { const options: HttpRequestOptions = { method: mergedConfig.method || HttpMethod.GET, header: mergedConfig.headers, extraData: mergedConfig.data, expectDataType: HttpDataType.OBJECT } return new Promise((resolve, reject) => { httpRequest.request( this.buildURL(mergedConfig), options, (err, data) => { httpRequest.destroy() if (err) { reject(this.normalizeError(err)) } else { resolve(this.normalizeResponse<T>(data)) } } ) }) } catch (err) { return Promise.reject(err) } } private buildURL(config: RequestConfig): string { // 处理URL拼接和参数序列化 let url = this.baseURL + config.url if (config.params) { url += '?' + new URLSearchParams(config.params).toString() } return url } }

关键点说明:

  1. 使用配置合并策略,允许全局默认值和单个请求特殊配置
  2. 统一的URL构建方法,规范参数处理
  3. 错误和响应的标准化处理

3. 实现拦截器系统

3.1 设计拦截器链

拦截器是网络库最强大的特性之一。在我们的电商App中,这些场景特别有用:

  • 请求签名:给重要接口添加数字签名
  • Token刷新:401错误时自动刷新token
  • 性能监控:记录关键接口耗时
  • 数据加解密:敏感字段自动加密

实现拦截器管理器:

class InterceptorManager { private interceptors: Interceptor[] = [] use(interceptor: Interceptor): number { this.interceptors.push(interceptor) return this.interceptors.length - 1 // 返回拦截器ID用于移除 } eject(id: number): void { this.interceptors[id] = null } async runRequestInterceptors(config: RequestConfig): Promise<RequestConfig> { let finalConfig = { ...config } for (const interceptor of this.interceptors) { if (interceptor?.onRequest) { finalConfig = await interceptor.onRequest(finalConfig) } } return finalConfig } async runResponseInterceptors<T>(response: ResponseData<T>): Promise<ResponseData<T>> { let finalResponse = { ...response } for (const interceptor of this.interceptors) { if (interceptor?.onResponse) { finalResponse = await interceptor.onResponse(finalResponse) } } return finalResponse } }

3.2 典型拦截器示例

Token自动刷新拦截器

const tokenRefreshInterceptor: Interceptor = { onError: async (error) => { if (error.status === 401 && !error.config._isRetry) { // 标记当前请求避免死循环 error.config._isRetry = true // 调用刷新token接口 const newToken = await refreshToken() // 更新后续请求的header error.config.headers.Authorization = `Bearer ${newToken}` // 重试原始请求 return http.request(error.config) } return Promise.reject(error) } }

请求日志拦截器

const loggerInterceptor: Interceptor = { onRequest: (config) => { console.log(`[Request] ${config.method} ${config.url}`, { params: config.params, data: config.data }) return config }, onResponse: (response) => { console.log(`[Response] ${response.code}`, response.data) return response } }

4. 高级功能实现

4.1 请求队列与并发控制

在商品秒杀场景下,突然爆发的请求可能导致服务器过载。我们需要实现:

class RequestQueue { private maxConcurrent: number private activeCount = 0 private queue: Array<() => Promise<any>> = [] constructor(maxConcurrent = 5) { this.maxConcurrent = maxConcurrent } add(requestFn: () => Promise<any>): Promise<any> { return new Promise((resolve, reject) => { const task = async () => { try { this.activeCount++ const result = await requestFn() resolve(result) } catch (error) { reject(error) } finally { this.activeCount-- this.next() } } this.queue.push(task) this.next() }) } private next() { while (this.activeCount < this.maxConcurrent && this.queue.length) { const task = this.queue.shift() task() } } }

使用方式:

const queue = new RequestQueue(3) // 限制3个并发 async function fetchProductDetail(id: string) { return queue.add(() => http.request({ url: `/products/${id}`, method: HttpMethod.GET }) ) }

4.2 智能重试机制

不是所有失败都值得重试,我们实现一个带策略的重试:

async function requestWithRetry<T>( config: RequestConfig, retryCount = 3 ): Promise<ResponseData<T>> { let lastError: Error for (let i = 0; i <= retryCount; i++) { try { const response = await http.request<T>({ ...config, // 指数退避 timeout: config.timeout * (i + 1) }) return response } catch (error) { lastError = error // 只有网络错误和5xx错误才重试 if (!isNetworkError(error) && !isServerError(error)) { break } if (i < retryCount) { await sleep(1000 * Math.pow(2, i)) // 指数退避等待 } } } return Promise.reject(lastError) }

5. 完整实现与最佳实践

5.1 组装完整网络库

现在我们把所有部件组合起来:

class HttpClient { private requestQueue = new RequestQueue() private interceptors = new InterceptorManager() constructor(private baseURL: string) {} async request<T>(config: RequestConfig): Promise<ResponseData<T>> { try { // 执行请求拦截器 const processedConfig = await this.interceptors.runRequestInterceptors(config) // 加入队列管理 const response = await this.requestQueue.add(() => makeActualRequest<T>(processedConfig) ) // 执行响应拦截器 return this.interceptors.runResponseInterceptors(response) } catch (error) { // 执行错误拦截器 for (const interceptor of this.interceptors) { if (interceptor?.onError) { return interceptor.onError(error) } } throw error } } // 添加便捷方法 get<T>(url: string, params?: object) { return this.request<T>({ url, method: HttpMethod.GET, params }) } post<T>(url: string, data?: object) { return this.request<T>({ url, method: HttpMethod.POST, data }) } use(interceptor: Interceptor) { return this.interceptors.use(interceptor) } }

5.2 在电商App中的典型应用

商品详情页的请求处理

// 初始化客户端 const http = new HttpClient('https://api.ecommerce.com') // 添加全局拦截器 http.use(tokenRefreshInterceptor) http.use(loggerInterceptor) http.use({ onRequest: (config) => { // 自动添加认证token if (!config.headers) config.headers = {} config.headers.Authorization = `Bearer ${getToken()}` return config } }) // 在页面中使用 async function loadProductDetail(productId: string) { try { const { data } = await http.get<Product>(`/products/${productId}`, { timeout: 5000, retry: 2 }) // 更新UI... } catch (error) { if (error.status === 404) { showProductNotFound() } else { showNetworkError() } } }

关键优化点

  1. 所有商品请求自动携带认证信息
  2. 重要接口设置较短超时和重试策略
  3. 统一的错误处理逻辑
  4. 完整的请求日志记录

6. 性能优化与调试技巧

6.1 网络性能监控

Interceptor中添加性能统计:

const perfInterceptor: Interceptor = { onRequest: (config) => { config.metadata = { startTime: Date.now() } return config }, onResponse: (response) => { const duration = Date.now() - response.config.metadata.startTime trackApiPerformance(response.config.url, duration) return response } }

6.2 调试技巧

开发阶段可以添加这些调试工具:

// 在开发环境添加调试拦截器 if (process.env.NODE_ENV === 'development') { http.use({ onRequest: (config) => { console.groupCollapsed(`%c ${config.method} ${config.url}`, 'color: #4CAF50') console.log('Params:', config.params) console.log('Data:', config.data) console.groupEnd() return config }, onResponse: (response) => { console.groupCollapsed(`%c Response ${response.code}`, 'color: #2196F3') console.log('Data:', response.data) console.groupEnd() return response } }) }

7. 安全加固方案

7.1 数据加密拦截器

对敏感接口启用加密:

const cryptoInterceptor: Interceptor = { onRequest: (config) => { if (config.needEncrypt) { config.data = { encrypted: encrypt(JSON.stringify(config.data)), timestamp: Date.now() } } return config }, onResponse: (response) => { if (response.data?.encrypted) { response.data = JSON.parse(decrypt(response.data.encrypted)) } return response } }

7.2 防CSRF处理

自动添加CSRF Token:

const csrfInterceptor: Interceptor = { onRequest: (config) => { if (config.method !== HttpMethod.GET) { config.headers['X-CSRF-Token'] = getCSRFToken() } return config } }

8. 测试策略

8.1 单元测试重点

为网络库编写测试时要覆盖:

  1. 基础请求功能
  2. 拦截器执行顺序
  3. 错误处理流程
  4. 队列管理逻辑
  5. 重试机制

示例测试用例:

describe('HttpClient', () => { let http: HttpClient let mockServer: MockServer beforeEach(() => { mockServer = new MockServer() http = new HttpClient(mockServer.getBaseUrl()) }) it('should execute request interceptors in order', async () => { const order: number[] = [] http.use({ onRequest: () => (order.push(1), {}) }) http.use({ onRequest: () => (order.push(2), {}) }) await http.get('/test') expect(order).toEqual([1, 2]) }) it('should retry on network failure', async () => { mockServer.setResponse('/retry', { status: 500 }) try { await http.get('/retry', { retry: 2 }) } catch (error) { expect(mockServer.getCallCount('/retry')).toBe(3) } }) })

8.2 压力测试建议

使用真实场景测试:

  1. 模拟100个用户同时抢购
  2. 测试token过期时的自动刷新
  3. 模拟弱网环境下的请求表现
  4. 验证内存泄漏情况

9. 与简单封装的对比

9.1 代码复杂度对比

传统方式在每个请求中处理错误:

async function getProduct(id: string) { try { const response = await fetch(`/products/${id}`) if (!response.ok) throw new Error('Request failed') const data = await response.json() if (data.code !== 200) { if (data.code === 401) { // 处理token过期 await refreshToken() return getProduct(id) } throw new Error(data.message) } return data } catch (error) { console.error('Failed to load product', error) throw error } }

使用我们的封装后:

// 初始化时配置一次 http.use(tokenRefreshInterceptor) http.use(errorHandlerInterceptor) // 业务代码变得极其简洁 function getProduct(id: string) { return http.get<Product>(`/products/${id}`) }

9.2 维护成本对比

当需要修改所有请求的超时时间时:

  • 传统方式:需要修改每个请求调用处
  • 我们的方案:只需修改默认配置或添加拦截器

10. 常见问题解决方案

10.1 内存泄漏预防

确保做到:

  1. 每个请求完成后销毁httpRequest对象
  2. 拦截器中避免保留请求引用
  3. 使用WeakMap存储请求元数据

10.2 取消请求实现

HarmonyOS目前没有内置的取消机制,我们可以模拟实现:

class CancelToken { private promise: Promise<never> private resolve: (reason?: any) => void constructor() { this.promise = new Promise((resolve) => { this.resolve = resolve }) } cancel(reason?: string) { this.resolve(new Error(reason || 'Request cancelled')) } get token(): Promise<never> { return this.promise } } // 使用方式 const cancelToken = new CancelToken() http.get('/data', { cancelToken: cancelToken.token }).catch(err => { if (err.message === 'Request cancelled') { console.log('请求被取消') } }) // 需要取消时 cancelToken.cancel()

11. 实际项目集成建议

11.1 目录结构规划

推荐这样组织代码:

src/ network/ http/ HttpClient.ts # 核心实现 interceptors/ # 各种拦截器 auth.ts logger.ts ... types/ # 类型定义 utils/ # 工具函数 index.ts # 入口文件

11.2 多环境配置

通过工厂方法创建不同环境的客户端:

function createHttpClient(env: 'dev' | 'prod') { const baseURL = { dev: 'https://dev.api.com', prod: 'https://api.com' }[env] const client = new HttpClient(baseURL) if (env === 'dev') { client.use(devLoggerInterceptor) } return client }

12. 扩展思路

12.1 WebSocket集成

可以扩展为统一的网络模块:

class NetworkManager { private http: HttpClient private websocket: WebSocketClient constructor(config: NetworkConfig) { this.http = new HttpClient(config.http) this.websocket = new WebSocketClient(config.ws) } // 统一的消息处理 onMessage(handler: (msg: Message) => void) { this.websocket.onMessage(handler) } }

12.2 离线缓存策略

添加缓存拦截器:

const cacheInterceptor: Interceptor = { onRequest: async (config) => { if (config.cache) { const cached = await cacheStore.get(config.url) if (cached) return cached } return config }, onResponse: (response) => { if (response.config.cache) { cacheStore.set(response.config.url, response) } return response } }

在电商App的商品详情页这种读多写少的场景特别有用,可以显著减少网络请求。

http://www.jsqmd.com/news/577434/

相关文章:

  • PX4开发环境一站式配置:源码、QGroundControl、MAVROS与ROS Melodic联调全记录
  • 用“目标→策略→动作→标准”四步法,把挂在墙上的目标,变成落在地上的结果
  • ESP32远程OTA升级踩坑实录:HTTPS证书处理、固件链接失效与阿里云配置的那些‘坑’(附避坑代码)
  • 115. OOM(内存不足),高内存消耗,基本故障排除步骤
  • 5大核心功能解析:GHelper轻量替代方案如何优化华硕笔记本性能
  • Mac上IntelliJ IDEA 2024.1.1启动报错?手把手教你删除-javaagent修复(附详细路径)
  • 知网AIGC检测算法2026年更新了什么这样降AI才有效
  • 收藏备用!小白程序员必看:从基础到进阶,彻底吃透Prompt与提示工程
  • Debian 12.0 + Nginx + Let’s Encrypt:5分钟搞定HTTPS配置(含自动续期)
  • 给技术人的另类书单:从《纳瓦尔宝典》的‘代码杠杆’谈到工程师的财富与幸福实践
  • 全球与中国边缘保护系统市场现状洞察与未来走向研判
  • 46397
  • 离线应急方案:OpenClaw断开网络时调用本地Qwen3-4B继续工作
  • 【电路】从零开始掌握大学电路核心定律与分析方法
  • 茉莉花插件:5分钟快速上手Zotero中文文献智能管理终极指南
  • 跨平台性能监控实战:从本地到服务器的全面指南
  • RTX 4090专属优化:造相-Z-Image防爆显存+BF16高精度实战解析
  • 【嵌入式实战】环形缓冲区在数据流处理中的核心应用与避坑指南
  • 保姆级教程:在Windows 10/11上搞定Carsim 2019.0安装与破解(含防火墙关闭和许可文件配置)
  • SEO优化教程网推广技巧有哪些
  • Windows 11 24H2 LTSC 微软商店部署指南:从原理到实践
  • 从零构建STM32F429智能控制终端:基于TouchGFX GUI与FreeRTOS的多任务IO调度实践
  • 告别编译报错!Ubuntu 22.04 LTS下x264库的保姆级安装指南(含configure参数详解)
  • FPGA项目实战:如何用PWM波同时搞定电机和舵机?Ego1避障小车中的双PWM控制核心解析
  • Qwen3-14B大模型落地实践:中小企业私有AI助手部署完整流程
  • 告别Permission denied!5分钟搞定GitHub多账号SSH密钥配置(含可视化操作指南)
  • 安卓逆向实战:Frida检测绕过与反制策略全解析
  • macOS Finder视频预览终极指南:QLVideo让专业视频管理触手可及
  • OFA 视觉问答(VQA)模型部署教学(避坑完整版)
  • 2026年名酒回收/洋酒回收/茅台酒/五粮液/陈年老酒高价上门现金回收服务专业推荐榜:诚信高效,价值兑现之选 - 品牌企业推荐师(官方)