网络请求是前端应用的重中之重,一个好的请求封装可以让开发事半功倍。本文将分享uni-app网络请求的最佳实践经验。
1. 基础请求封装
1.1 创建请求实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   |  const request = (options = {}) => {   return new Promise((resolve, reject) => {     uni.request({       url: process.env.VUE_APP_BASE_API + options.url,       method: options.method || 'GET',       data: options.data || {},       header: options.header || {},       timeout: options.timeout || 60000,       success: (res) => resolve(res.data),       fail: (err) => reject(err)     })   }) }
  export default request
 
  | 
 
1.2 添加拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | const httpInterceptor = {      invoke(options) {          if (!options.url.startsWith('http')) {       options.url = baseURL + options.url     }          options.timeout = 10000          options.header = {       ...options.header,       'source-client': 'miniapp'     }          const token = uni.getStorageSync('token')     if (token) {       options.header.Authorization = `Bearer ${token}`     }     return options   } }
 
  uni.addInterceptor('request', httpInterceptor) uni.addInterceptor('uploadFile', httpInterceptor)
  | 
 
2. 常见错误处理
2.1 统一错误处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
   | const errorHandle = (response) => {   const status = response.statusCode      switch (status) {     case 401:              uni.navigateTo({         url: '/pages/login/index'       })       break     case 403:              uni.showToast({         title: '服务器拒绝执行',         icon: 'none'       })       break     case 404:              uni.showToast({         title: '请求的资源不存在',         icon: 'none'       })       break     default:              uni.showToast({         title: response.data.message || '请求失败',         icon: 'none'       })   }   return Promise.reject(response) }
  | 
 
2.2 请求重试机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | const retry = (fn, times = 3, delay = 1000) => {   return new Promise((resolve, reject) => {     function attempt() {       fn().then(resolve).catch(err => {         console.log(`请求失败,剩余重试次数:${times - 1}`)         if (times - 1 > 0) {           times--           setTimeout(attempt, delay)         } else {           reject(err)         }       })     }     attempt()   }) }
 
  retry(() => request({   url: '/api/data',   method: 'GET' })).then(res => {   console.log('请求成功', res) }).catch(err => {   console.log('重试后依然失败', err) })
  | 
 
3. Token刷新方案
3.1 无感刷新Token
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
   | let isRefreshing = false let requests = []
  const refreshToken = () => {   if (isRefreshing) {          return new Promise((resolve) => {       requests.push((token) => {         resolve(token)       })     })   }   isRefreshing = true      return new Promise((resolve, reject) => {     request({       url: '/auth/refresh',       method: 'POST',       data: {         refresh_token: uni.getStorageSync('refresh_token')       }     }).then(res => {       isRefreshing = false              uni.setStorageSync('token', res.token)              requests.forEach(cb => cb(res.token))       requests = []       resolve(res.token)     }).catch(err => {       isRefreshing = false              uni.clearStorageSync()              uni.navigateTo({         url: '/pages/login/index'       })       reject(err)     })   }) }
   | 
 
3.2 请求拦截中应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | const httpInterceptor = {   invoke(options) {                    if (isTokenExpired() && !options.url.includes('/auth')) {       return refreshToken().then(token => {                  options.header.Authorization = `Bearer ${token}`         return uni.request(options)       })     }          return options   } }
  | 
 
4. 上传下载处理
4.1 文件上传封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
   | const upload = (options = {}) => {   return new Promise((resolve, reject) => {     uni.uploadFile({       url: process.env.VUE_APP_BASE_API + options.url,       filePath: options.filePath,       name: options.name || 'file',       header: {         ...options.header,         'Content-Type': 'multipart/form-data'       },       success: (res) => resolve(JSON.parse(res.data)),       fail: (err) => reject(err)     })   }) }
  | 
 
4.2 文件下载封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | const download = (options = {}) => {   return new Promise((resolve, reject) => {     uni.downloadFile({       url: options.url,       header: options.header,       success: (res) => {         if (res.statusCode === 200) {           resolve(res.tempFilePath)         } else {           reject(new Error('下载失败'))         }       },       fail: (err) => reject(err)     })   }) }
  | 
 
5. 使用示例
5.1 基础请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
   |  import request from '@/utils/request'
  export const getUserInfo = () => {   return request({     url: '/user/info',     method: 'GET'   }) }
 
  async onLoad() {   try {     const userInfo = await getUserInfo()     this.userInfo = userInfo   } catch (err) {     console.error(err)   } }
 
  | 
 
5.2 文件上传
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   |  const chooseAndUpload = async () => {   try {     const [tempFile] = await uni.chooseImage({       count: 1     })     const res = await upload({       url: '/upload',       filePath: tempFile.path,       name: 'file'     })     console.log('上传成功', res)   } catch (err) {     console.error('上传失败', err)   } }
 
  | 
 
6. 总结
- 统一封装请求方法
 
- 添加请求/响应拦截器
 
- 实现错误处理和重试机制
 
- 处理token刷新
 
- 封装上传下载方法
 
如果觉得文章对你有帮助,欢迎点赞、评论、分享,你的支持是我继续创作的动力!