uni-app网络请求最佳实践:封装、拦截与错误处理 0 次阅读

网络请求是前端应用的重中之重,一个好的请求封装可以让开发事半功倍。本文将分享uni-app网络请求的最佳实践经验。

1. 基础请求封装

1.1 创建请求实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// utils/request.js
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) {
// 1. 非http开头需拼接地址
if (!options.url.startsWith('http')) {
options.url = baseURL + options.url
}
// 2. 请求超时
options.timeout = 10000
// 3. 添加小程序端请求头标识
options.header = {
...options.header,
'source-client': 'miniapp'
}
// 4. 添加token请求头标识
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:
// token失效,重新登录
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) {
// 返回Promise,将其他请求缓存
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
// 更新token
uni.setStorageSync('token', res.token)
// 执行缓存的请求
requests.forEach(cb => cb(res.token))
requests = []
resolve(res.token)
}).catch(err => {
isRefreshing = false
// token刷新失败,清空缓存
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) {
// ... 其他配置

// 判断token是否过期
if (isTokenExpired() && !options.url.includes('/auth')) {
return refreshToken().then(token => {
// 更新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
// api/user.js
import request from '@/utils/request'

export const getUserInfo = () => {
return request({
url: '/user/info',
method: 'GET'
})
}

// pages/index/index.vue
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. 总结

  1. 统一封装请求方法
  2. 添加请求/响应拦截器
  3. 实现错误处理和重试机制
  4. 处理token刷新
  5. 封装上传下载方法

如果觉得文章对你有帮助,欢迎点赞、评论、分享,你的支持是我继续创作的动力!

上一篇 uni-app数据存储全攻略:从本地存储到持久化方案
下一篇 uni-app生命周期踩坑指南:常见错误与最佳实践
感谢您的支持!
微信赞赏码 微信赞赏
支付宝赞赏码 支付宝赞赏