封装 uni.request
的通用请求工具
在开发微信小程序时,网络请求是不可避免的一部分。为了简化代码、提高可维护性,我们可以封装一个通用的请求工具。本文将详细介绍如何封装一个支持自动重试、处理 token 过期以及错误处理的请求工具。
需求背景
封装一个通用的请求工具可以大大简化网络请求的代码,提高代码的可维护性和复用性。通过统一的错误处理和token管理机制,可以避免重复代码,减少错误,提高开发效率。此外,通过处理
token过期和自动重试机制,可以提高应用的可靠性和用户体验。通过将每个请求封装在独立的文件中,可以使代码更加模块化,方便管理和维护。
目录结构
我们将创建以下目录和文件:
api/
index.js
:导出所有API请求函数。user.js
:封装用户相关的请求。auth.js
:处理 token 获取和刷新逻辑。errorHandler.js
:处理不同的错误逻辑。request.js
:封装网络请求的核心逻辑。
request.js
这是我们的主要请求工具文件,负责处理网络请求,自动重试,处理 token 过期等。
// api/request.js
import { handleError } from './errorHandler.js';
import { getTokens, refreshToken, login } from './auth.js';
const baseURL = 'https://your-api-base-url.com'; // 替换为你的API基础URL
/**
* 通用请求函数
* @param {Object} options 请求选项
* @param {number} retryCount 最大重试次数,默认2次
* @returns {Promise} 返回一个Promise对象
*/
const request = (options, retryCount = 2) => {
return new Promise((resolve, reject) => {
const { url, method = 'GET', data = {}, header = { 'Content-Type': 'application/json' }, timeout = 10000, retry = true } = options;
const tokens = getTokens();
/**
* 发起请求的内部函数
* @param {number} retryAttempt 当前重试次数
*/
const makeRequest = (retryAttempt) => {
uni.request({
url: `${baseURL}${url}`, // 完整的请求URL
method, // 请求方法,默认为GET
data, // 请求数据,默认为空对象
header: {
...header, // 合并传入的header
Authorization: `Bearer ${tokens.accessToken || ''}`, // 添加授权头
},
timeout, // 请求超时时间,默认为10秒
success: (res) => {
if (res.statusCode === 200) {
resolve(res.data); // 请求成功,解析Promise
} else if (res.statusCode === 401) {
// 处理未授权错误
handleUnauthorized(retryAttempt, resolve, reject, options);
} else {
handleError(res); // 调用错误处理函数
reject(res); // 请求失败,拒绝Promise
}
},
fail: (err) => {
handleError(err); // 调用错误处理函数
if (retry && retryAttempt < retryCount) {
// 如果需要重试且未达到最大重试次数,重新发起请求
makeRequest(retryAttempt + 1);
} else {
reject(err); // 请求失败,拒绝Promise
}
},
});
};
// 初次调用请求函数
makeRequest(0);
});
};
/**
* 处理未授权错误
* @param {number} retryAttempt 当前重试次数
* @param {function} resolve Promise的resolve函数
* @param {function} reject Promise的reject函数
* @param {Object} options 请求选项
*/
const handleUnauthorized = (retryAttempt, resolve, reject, options) => {
// 调用刷新token函数
refreshToken().then(() => {
// 刷新token成功,重新发起请求
request(options).then(resolve).catch(reject);
}).catch((err) => {
// 刷新token失败,进行登录逻辑
console.error('刷新token失败:', err);
login(); // 调用登录函数
reject(new Error('未授权,需要登录')); // 拒绝Promise
});
};
export default request;
auth.js
这个文件处理 token 的获取、存储和刷新逻辑。
// api/auth.js
/**
* 获取本地存储的token
* @returns {Object} 返回包含accessToken和refreshToken的对象
*/
export const getTokens = () => {
return {
accessToken: uni.getStorageSync('accessToken'),
refreshToken: uni.getStorageSync('refreshToken')
};
};
/**
* 设置本地存储的token
* @param {string} accessToken 新的accessToken
* @param {string} refreshToken 新的refreshToken
*/
export const setTokens = (accessToken, refreshToken) => {
uni.setStorageSync('accessToken', accessToken);
uni.setStorageSync('refreshToken', refreshToken);
};
/**
* 刷新token
* @returns {Promise} 返回一个Promise对象,刷新成功则解析,否则拒绝
*/
export const refreshToken = () => {
return new Promise((resolve, reject) => {
const { refreshToken } = getTokens();
uni.request({
url: 'https://your-api-base-url.com/refresh-token', // 替换为刷新token的URL
method: 'POST',
data: { token: refreshToken },
success: (res) => {
if (res.statusCode === 200) {
const { accessToken, refreshToken } = res.data;
setTokens(accessToken, refreshToken);
resolve();
} else {
reject(new Error('刷新token失败'));
}
},
fail: (err) => {
reject(err);
}
});
});
};
/**
* 登录逻辑
*/
export const login = () => {
// 你的登录逻辑,这里是一个示例
uni.login({
provider: 'weixin',
success: (loginRes) => {
// 假设调用登录接口获取token
uni.request({
url: 'https://your-api-base-url.com/login', // 替换为登录的URL
method: 'POST',
data: { code: loginRes.code },
success: (res) => {
if (res.statusCode === 200) {
const { accessToken, refreshToken } = res.data;
setTokens(accessToken, refreshToken);
uni.showToast({ title: '登录成功', icon: 'none' });
} else {
uni.showToast({ title: '登录失败', icon: 'none' });
}
},
fail: () => {
uni.showToast({ title: '登录失败', icon: 'none' });
}
});
}
});
};
errorHandler.js
这个文件定义了不同的错误处理逻辑。
// api/errorHandler.js
/**
* 处理错误
* @param {Object} error 错误对象
*/
export const handleError = (error) => {
if (error.statusCode) {
switch (error.statusCode) {
case 401:
// 未授权错误在request.js中已经处理,不需要在这里处理
break;
case 404:
handleNotFoundError();
break;
default:
handleDefaultError();
break;
}
} else {
handleNetworkError();
}
};
/**
* 处理未找到错误
*/
const handleNotFoundError = () => {
uni.showToast({ title: '请求地址不存在', icon: 'none' });
};
/**
* 处理默认错误
*/
const handleDefaultError = () => {
uni.showToast({ title: '请求失败,请稍后再试', icon: 'none' });
};
/**
* 处理网络错误
*/
const handleNetworkError = () => {
uni.showToast({ title: '网络错误,请检查您的网络连接', icon: 'none' });
};
user.js
这个文件封装用户相关的请求,例如登录、获取用户信息等。
// api/user.js
import request from './request';
/**
* 用户登录
* @param {Object} data 登录数据
* @returns {Promise} 返回一个Promise对象
*/
export const login = (data) => {
return request({
url: '/login',
method: 'POST',
data,
});
};
/**
* 获取用户信息
* @returns {Promise} 返回一个Promise对象
*/
export const getUserInfo = () => {
return request({
url: '/user/info',
method: 'GET',
});
};
index.js
这个文件导出所有API请求函数。
// api/index.js
import * as user from './user';
export default {
user,
};
使用示例
在页面或组件中使用这个工具时,只需要调用相应的函数即可:
import api from './api';
export default {
data() {
return {
responseData: null,
};
},
methods: {
fetchData() {
api.user.getUserInfo().then((data) => {
this.responseData = data;
}).catch((error) => {
console.error('请求失败:', error);
});
}
},
mounted() {
this.fetchData();
}
};
详细解释
-
目录结构:将所有API相关的逻辑放在
api
文件夹中,每个请求封装在独立的文件中,方便管理和维护。 -
request.js
:封装通用的请求逻辑,包括自动重试和处理 token 过期。 -
auth.js
:处理 token 的获取、存储和刷新逻辑,提供getTokens
、setTokens
、refreshToken
和login
函数。 -
errorHandler.js
:定义错误处理逻辑,根据不同的错误类型执行不同的处理。 -
user.js
:封装用户相关的请求,例如登录和获取用户信息。 -
index.js
:导出所有API请求函数,方便在项目中统一调用。