小程序中如何对网络请求进行二次封装

发布时间:2021-11-02 11:34:25 作者:小新
来源:亿速云 阅读:242

小程序中如何对网络请求进行二次封装

目录

  1. 引言
  2. 为什么要进行二次封装
  3. 基础封装实现
  4. 添加请求拦截器
  5. 添加响应拦截器
  6. 错误处理机制
  7. 请求取消功能
  8. 缓存策略实现
  9. TypeScript支持
  10. 性能优化
  11. 测试与调试
  12. 完整代码示例
  13. 总结

引言

在小程序开发中,网络请求是与后端交互的核心功能。微信小程序提供了wx.requestAPI用于发起网络请求,但在实际项目中直接使用原生API会面临诸多问题。本文将详细介绍如何对小程序中的网络请求进行二次封装,打造一个功能完善、易于维护的请求库。

为什么要进行二次封装

直接使用wx.request存在以下问题:

  1. 代码冗余:每个请求都需要重复编写基础配置
  2. 难以维护:请求逻辑分散在各个页面,修改困难
  3. 缺乏统一处理:错误处理、loading状态等无法统一管理
  4. 功能单一:缺少拦截器、缓存等高级功能

二次封装可以带来以下优势:

基础封装实现

我们先创建一个基础的请求类,封装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
})

错误处理机制

完善的错误处理是网络请求的关键,我们需要处理以下几种错误:

  1. 网络错误
  2. 超时错误
  3. HTTP状态码错误
  4. 业务逻辑错误
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支持

为我们的请求库添加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

性能优化

  1. 合并请求:对于多个小请求可以合并为一个
  2. 请求去重:防止重复请求
  3. 预请求:提前加载可能需要的资源
  4. 压缩数据:与服务端协商使用压缩传输
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 })

推荐阅读:
  1. 小程序-网络请求封装
  2. 微信小程序中如何使用flyio封装网络请求

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

小程序

上一篇:Python的内置函数有哪些

下一篇:Mysql索引失效的解决方法

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》