网络请求还能这么优雅?luch-request 封装库,比 uni.request 好用 10 倍
引言在 uni-app 项目中网络请求是最基础也最高频的操作。虽然 uni-app 提供了uni.requestAPI但每次都要手动处理拦截器、错误提示、Token 刷新、请求取消等逻辑代码重复且容易出错。今天推荐的这个 DCloud 插件市场上的luch-request是一个基于 Promise 的 HTTP 请求库。它对uni.request进行了深度封装提供了拦截器、全局配置、请求取消、文件上传下载等强大功能让你的网络请求代码更加优雅和高效。实战案例电商平台接口统一封装背景我们正在开发一个电商平台包含商品、订单、支付、用户等多个模块需要调用大量后端接口。问题每个接口都要重复处理 Token、错误提示、加载状态代码冗余严重。而且不同模块的请求配置不一致维护困难。解决方案我们引入了 luch-request统一封装了请求拦截器、响应拦截器、错误处理、Token 自动刷新等逻辑各模块只需关注业务代码。结果网络请求相关代码量减少了 60%接口调用更加简洁错误处理更加统一团队协作效率大幅提升。核心功能一览luch-request 提供了丰富的功能几乎涵盖了网络请求的所有场景。Promise 支持原生支持 Promise代码更简洁全局配置统一配置 baseURL、timeout、header 等请求拦截器统一添加 Token、参数签名等响应拦截器统一处理错误、数据转换等文件上传下载支持进度回调大文件处理更轻松请求取消支持取消未完成的请求避免内存泄漏TypeScript 支持完整的类型定义开发体验更好多实例支持可创建多个请求实例适配不同后端如何快速跑起来开发者提供了非常详尽的文档这里简要概括步骤。安装插件在 HBuilderX 中打开项目在插件市场搜索luch-request点击安装选择安装到当前项目初始化配置创建request.js文件配置 baseURL、timeout、header 等全局参数添加请求/响应拦截器使用请求在需要的地方引入封装好的 request调用request.get()、request.post()等方法处理返回的 Promise可直接运行的代码下面是一个完整的请求封装示例包含了初始化配置、拦截器、Token 自动刷新、文件上传等功能。你可以直接复制到你的项目中使用。// utils/request.js import Request from/components/luch-request/request.js; const http new Request(); // 全局配置 http.setConfig((config) { config.baseURL https://api.example.com; // 基础路径 config.timeout 10000; // 超时时间 config.header { ...config.header, Content-Type: application/json }; return config; }); // 请求拦截器 http.interceptors.request.use((config) { // 添加 Token const token uni.getStorageSync(token); if (token) { config.header.Authorization Bearer ${token}; } // 添加请求时间戳防止缓存 config.params { ...config.params, _t: Date.now() }; // 显示加载提示可选 uni.showLoading({ title: 加载中..., mask: true }); return config; }, (error) { // 隐藏加载提示 uni.hideLoading(); returnPromise.reject(error); }); // 响应拦截器 http.interceptors.response.use((response) { // 隐藏加载提示 uni.hideLoading(); const { data, statusCode } response; // 根据状态码处理 if (statusCode 200) { if (data.code 0) { // 业务成功 return data.data; } elseif (data.code 401) { // Token 过期自动刷新 return refreshToken().then(() { // 重试原请求 return http.request(response.config); }); } else { // 业务错误 uni.showToast({ title: data.msg || 请求失败, icon: none }); returnPromise.reject(newError(data.msg)); } } else { // HTTP 错误 uni.showToast({ title: 网络错误${statusCode}, icon: none }); returnPromise.reject(newError(HTTP ${statusCode})); } }, (error) { // 隐藏加载提示 uni.hideLoading(); // 网络错误处理 uni.showToast({ title: 网络连接失败请检查网络, icon: none }); returnPromise.reject(error); }); // Token 刷新函数 asyncfunction refreshToken() { const refreshToken uni.getStorageSync(refresh_token); if (!refreshToken) { // 没有刷新 Token跳转到登录页 uni.reLaunch({ url: /pages/login/login }); returnPromise.reject(newError(未登录)); } try { const res await http.post(/auth/refresh, { refreshToken }); // 保存新 Token uni.setStorageSync(token, res.token); uni.setStorageSync(refresh_token, res.refreshToken); return res; } catch (error) { // 刷新失败跳转到登录页 uni.reLaunch({ url: /pages/login/login }); returnPromise.reject(error); } } exportdefault http;!-- 使用示例pages/product/list.vue -- template view classcontainer view v-ifloading classloading加载中.../view view v-else-iferror classerror{{ error }}/view view v-else classproduct-list view v-foritem in products :keyitem.id classproduct-item clickgoDetail(item.id) image classproduct-image :srcitem.image modeaspectFill/image view classproduct-info text classproduct-name{{ item.name }}/text text classproduct-price¥{{ item.price }}/text text classproduct-sales已售 {{ item.sales }}/text /view /view /view view classload-more v-ifhasMore clickloadMore text加载更多/text /view /view /template script import http from /utils/request.js; export default { data() { return { products: [], loading: false, error: , page: 1, pageSize: 10, hasMore: true } }, onLoad() { this.loadProducts(); }, onPullDownRefresh() { this.refreshProducts(); }, onReachBottom() { if (this.hasMore) { this.loadMore(); } }, methods: { // 加载商品列表 async loadProducts() { if (this.loading) return; this.loading true; this.error ; try { const res await http.get(/product/list, { params: { page: this.page, pageSize: this.pageSize } }); this.products res.list; this.hasMore res.hasMore; } catch (error) { this.error error.message; } finally { this.loading false; } }, // 刷新列表 async refreshProducts() { this.page 1; await this.loadProducts(); uni.stopPullDownRefresh(); }, // 加载更多 async loadMore() { this.page; await this.loadProducts(); }, // 跳转详情 goDetail(id) { uni.navigateTo({ url: /pages/product/detail?id${id} }); }, // 文件上传示例 async uploadImage(filePath) { try { const res await http.upload(/upload/image, { filePath: filePath, name: file, formData: { type: product } }); return res.url; } catch (error) { console.error(上传失败, error); throw error; } } } } /script style .container { padding: 20rpx; } .loading, .error { text-align: center; padding: 100rpx 0; color: #999; } .error { color: #ff4d4f; } .product-list { display: flex; flex-direction: column; } .product-item { display: flex; margin-bottom: 20rpx; background-color: #fff; border-radius: 10rpx; overflow: hidden; box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); } .product-image { width: 200rpx; height: 200rpx; } .product-info { flex: 1; padding: 20rpx; display: flex; flex-direction: column; justify-content: space-between; } .product-name { font-size: 30rpx; color: #333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .product-price { font-size: 32rpx; color: #e64340; font-weight: bold; } .product-sales { font-size: 24rpx; color: #999; } .load-more { text-align: center; padding: 30rpx; color: #007aff; } /style踩坑记录问题 1在小程序端上传大文件时提示上传超时。解决办法大文件上传需要增加 timeout 配置。可以在上传时单独配置超时时间http.upload(url, { timeout: 60000 })。同时建议在后端也调整超时配置。问题 2Token 刷新后并发请求重复刷新。解决办法使用一个标志位isRefreshing标记刷新状态刷新期间的请求先缓存等刷新完成后再统一重试。参考代码中的队列处理逻辑。问题 3请求取消后Promise 状态未正确处理。解决办法luch-request 支持请求取消调用request.abort()可以取消请求。需要在 catch 中处理取消异常避免未捕获的 Promise rejection。// 请求取消示例 const requestTask http.get(/api/data); // 在适当的时候取消 requestTask.abort();建议先收藏用到的时候直接来查。项目地址https://ext.dcloud.net.cn/plugin?id392