在小程序开发中,网络请求是与后端交互的核心功能。微信小程序提供了wx.requestAPI用于发起网络请求,但在实际项目中直接使用原生API会面临诸多问题。本文将详细介绍如何对小程序中的网络请求进行二次封装,打造一个功能完善、易于维护的请求库。
直接使用wx.request存在以下问题:
二次封装可以带来以下优势:
我们先创建一个基础的请求类,封装wx.request的基本功能:
// utils/request.js class Request { constructor(config = {}) { // 默认配置 this.defaultConfig = { baseURL: '', // 基础路径 timeout: 60000, // 超时时间 header: { 'content-type': 'application/json' // 默认请求头 } } // 合并配置 this.config = Object.assign({}, this.defaultConfig, config) } request(options) { // 合并请求配置 const mergedOptions = Object.assign({}, this.config, options) return new Promise((resolve, reject) => { wx.request({ ...mergedOptions, success: (res) => { resolve(res.data) }, fail: (err) => { reject(err) } }) }) } get(url, data, options = {}) { return this.request({ url, data, method: 'GET', ...options }) } post(url, data, options = {}) { return this.request({ url, data, method: 'POST', ...options }) } // 其他HTTP方法... } // 创建实例并导出 const request = new Request({ baseURL: 'https://api.example.com' }) export default request 使用方式:
import request from '@/utils/request' // GET请求 request.get('/user/info', {id: 123}).then(res => { console.log(res) }) // POST请求 request.post('/user/create', {name: '张三'}).then(res => { console.log(res) }) 请求拦截器可以在请求发出前对配置进行修改或执行一些公共操作:
class Request { constructor(config = {}) { // ...其他代码 this.interceptors = { request: [], response: [] } } // 添加请求拦截器 useRequestInterceptor(fulfilled, rejected) { this.interceptors.request.push({ fulfilled, rejected }) } // 添加响应拦截器 useResponseInterceptor(fulfilled, rejected) { this.interceptors.response.push({ fulfilled, rejected }) } async request(options) { // 请求拦截器处理 let requestOptions = Object.assign({}, this.config, options) for (const interceptor of this.interceptors.request) { try { requestOptions = await interceptor.fulfilled(requestOptions) || requestOptions } catch (error) { interceptor.rejected(error) return Promise.reject(error) } } return new Promise((resolve, reject) => { wx.request({ ...requestOptions, success: async (res) => { // 响应拦截器处理 let response = res for (const interceptor of this.interceptors.response) { try { response = await interceptor.fulfilled(response) || response } catch (error) { interceptor.rejected(error) reject(error) return } } resolve(response.data) }, fail: (err) => { reject(err) } }) }) } } 使用拦截器示例:
// 添加请求拦截器 - 添加token request.useRequestInterceptor(config => { const token = wx.getStorageSync('token') if (token) { config.header = config.header || {} config.header.Authorization = `Bearer ${token}` } return config }) // 添加响应拦截器 - 处理错误状态码 request.useResponseInterceptor(response => { if (response.statusCode !== 200) { throw new Error(`请求失败,状态码:${response.statusCode}`) } return response }, error => { wx.showToast({ title: '网络错误', icon: 'none' }) return Promise.reject(error) }) 响应拦截器可以统一处理响应数据、错误等:
// 在Request类中添加响应拦截器支持 // 上面已经包含在request方法中 // 使用示例:统一处理业务错误码 request.useResponseInterceptor(response => { const { code, message } = response.data if (code !== 0) { wx.showToast({ title: message || '业务错误', icon: 'none' }) throw new Error(message || '业务错误') } return response }) 完善的错误处理是网络请求的关键,我们需要处理以下几种错误:
class Request { constructor(config = {}) { // ...其他代码 // 默认错误处理 this.defaultErrorHandler = (error) => { console.error('Request Error:', error) wx.showToast({ title: '网络请求失败', icon: 'none' }) } } setErrorHandler(handler) { this.defaultErrorHandler = handler } async request(options) { try { // ...拦截器处理 const response = await new Promise((resolve, reject) => { const requestTask = wx.request({ ...requestOptions, success: resolve, fail: reject }) // 超时处理 if (requestOptions.timeout) { setTimeout(() => { requestTask.abort() reject(new Error('请求超时')) }, requestOptions.timeout) } }) // ...响应拦截器处理 return response.data } catch (error) { this.defaultErrorHandler(error) return Promise.reject(error) } } } 在某些场景下,我们需要取消正在进行的请求:
class Request { constructor(config = {}) { // ...其他代码 this.requestTasks = new Map() } request(options) { return new Promise((resolve, reject) => { const requestTask = wx.request({ ...options, success: (res) => { this.requestTasks.delete(options.url) resolve(res) }, fail: (err) => { this.requestTasks.delete(options.url) reject(err) } }) // 存储请求任务 this.requestTasks.set(options.url, requestTask) }) } // 取消请求 cancelRequest(url) { const task = this.requestTasks.get(url) if (task) { task.abort() this.requestTasks.delete(url) } } // 取消所有请求 cancelAllRequests() { this.requestTasks.forEach(task => { task.abort() }) this.requestTasks.clear() } } 使用示例:
// 发起请求 const promise = request.get('/api/data') // 取消请求 request.cancelRequest('/api/data') // 取消所有请求 request.cancelAllRequests() 对于一些不常变的数据,可以添加缓存功能:
class Request { constructor(config = {}) { // ...其他代码 this.cache = new Map() this.defaultCacheConfig = { enable: false, expire: 5 * 60 * 1000 // 默认5分钟 } } async get(url, data, options = {}) { const cacheKey = this.generateCacheKey(url, data) const cacheConfig = Object.assign({}, this.defaultCacheConfig, options.cache) // 检查缓存 if (cacheConfig.enable && this.cache.has(cacheKey)) { const { expireTime, data } = this.cache.get(cacheKey) if (Date.now() < expireTime) { return Promise.resolve(data) } this.cache.delete(cacheKey) } // 发起请求 return this.request({ url, data, method: 'GET', ...options }).then(res => { // 缓存结果 if (cacheConfig.enable) { this.cache.set(cacheKey, { data: res, expireTime: Date.now() + cacheConfig.expire }) } return res }) } // 生成缓存key generateCacheKey(url, data) { return `${url}:${JSON.stringify(data)}` } // 清除缓存 clearCache(key) { if (key) { this.cache.delete(key) } else { this.cache.clear() } } } 使用示例:
// 启用缓存 request.get('/api/data', null, { cache: { enable: true, expire: 10 * 60 * 1000 // 10分钟 } }) // 清除特定缓存 request.clearCache('/api/data:null') // 清除所有缓存 request.clearCache() 为我们的请求库添加TypeScript类型支持:
// types/request.d.ts interface RequestConfig { baseURL?: string timeout?: number header?: Record<string, string> data?: any method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'TRACE' | 'CONNECT' dataType?: string responseType?: string enableCache?: boolean cacheExpire?: number } interface Interceptor<V> { fulfilled: (value: V) => V | Promise<V> rejected?: (error: any) => any } declare class Request { constructor(config?: RequestConfig) request<T = any>(options: RequestConfig): Promise<T> get<T = any>(url: string, data?: any, options?: RequestConfig): Promise<T> post<T = any>(url: string, data?: any, options?: RequestConfig): Promise<T> put<T = any>(url: string, data?: any, options?: RequestConfig): Promise<T> delete<T = any>(url: string, data?: any, options?: RequestConfig): Promise<T> useRequestInterceptor(fulfilled: (config: RequestConfig) => RequestConfig | Promise<RequestConfig>, rejected?: (error: any) => any): void useResponseInterceptor(fulfilled: (response: any) => any, rejected?: (error: any) => any): void cancelRequest(url: string): void cancelAllRequests(): void clearCache(key?: string): void } declare const request: Request export default request class Request { constructor(config = {}) { // ...其他代码 this.pendingRequests = new Map() } async request(options) { const requestKey = this.generateRequestKey(options) // 检查是否有相同的请求正在处理 if (this.pendingRequests.has(requestKey)) { return this.pendingRequests.get(requestKey) } const promise = this._request(options).finally(() => { this.pendingRequests.delete(requestKey) }) this.pendingRequests.set(requestKey, promise) return promise } // 生成请求唯一key generateRequestKey(options) { return `${options.method}:${options.url}:${JSON.stringify(options.data)}` } // 预加载 prefetch(url, data = {}, options = {}) { return this.get(url, data, { ...options, enableCache: true }) } } 为我们的请求库编写测试用例:
// request.test.js describe('Request', () => { let request beforeEach(() => { request = new Request({ baseURL: 'https://jsonplaceholder.typicode.com' }) }) it('should make GET request', async () => { const data = await request.get('/todos/1') expect(data).toHaveProperty('id') }) it('should handle errors', async () => { await expect(request.get('/invalid-url')).rejects.toThrow() }) it('should work with interceptors', async () => { request.useRequestInterceptor(config => { config.header = config.header || {} config.header['X-Test'] = 'test' return config }) request.useResponseInterceptor(response => { response.data.test = true return response }) const data = await request.get('/todos/1') expect(data.test).toBe(true) }) // 更多测试用例... }) ”`javascript // utils/request.js
class Request { constructor(config = {}) { // 默认配置 this.defaultConfig = { baseURL: “, timeout: 60000, header: { ‘content-type’: ‘application/json’ } }
// 合并配置 this.config = Object.assign({}, this.defaultConfig, config) // 拦截器 this.interceptors = { request: [], response: [] } // 请求任务 this.requestTasks = new Map() // 请求缓存 this.cache = new Map() this.defaultCacheConfig = { enable: false, expire: 5 * 60 * 1000 } // 默认错误处理 this.defaultErrorHandler = (error) => { console.error('Request Error:', error) wx.showToast({ title: '网络请求失败', icon: 'none' }) } }
// 设置错误处理器 setErrorHandler(handler) { this.defaultErrorHandler = handler }
// 添加请求拦截器 useRequestInterceptor(fulfilled, rejected) { this.interceptors.request.push({ fulfilled, rejected }) }
// 添加响应拦截器 useResponseInterceptor(fulfilled, rejected) { this.interceptors.response.push({ fulfilled, rejected }) }
// 核心请求方法 async request(options) { try { // 合并配置 let requestOptions = Object.assign({}, this.config, options)
// 请求拦截器 for (const interceptor of this.interceptors.request) { try { requestOptions = await interceptor.fulfilled(requestOptions) || requestOptions } catch (error) { interceptor.rejected(error) return Promise.reject(error) } } // 生成缓存key const cacheKey = this.generateCacheKey(requestOptions) const cacheConfig = Object.assign({}, this.defaultCacheConfig, requestOptions.cache) // 检查缓存 if (requestOptions.method === 'GET' && cacheConfig.enable && this.cache.has(cacheKey)) { const { expireTime, data } = this.cache.get(cacheKey) if (Date.now() < expireTime) { return Promise.resolve(data) } this.cache.delete(cacheKey) } // 发起请求 const response = await new Promise((resolve, reject) => { const requestTask = wx.request({ ...requestOptions, success: resolve, fail: reject }) // 存储请求任务 this.requestTasks.set(cacheKey, requestTask) // 超时处理 if (requestOptions.timeout) { setTimeout(() => { requestTask.abort() reject(new Error('请求超时')) }, requestOptions.timeout) } }) // 响应拦截器 let processedResponse = response for (const interceptor of this.interceptors.response) { try { processedResponse = await interceptor.fulfilled(processedResponse) || processedResponse } catch (error) { interceptor.rejected(error) return Promise.reject(error) } } // 缓存响应数据 if (requestOptions.method === 'GET' && cacheConfig.enable) { this.cache.set(cacheKey, { data: processedResponse.data, expireTime: Date.now() + cacheConfig.expire }) } return processedResponse.data } catch (error) { this.defaultErrorHandler(error) return Promise.reject(error) } finally { // 清理请求任务 const cacheKey = this.generateCacheKey(options) this.requestTasks.delete(cacheKey) } }
// HTTP方法快捷方式 get(url, data, options = {}) { return this.request({ url, data, method: ‘GET’, …options })
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。