本节导读:数据流的第四层是服务器,而通往服务器的每一条路都该经过同一座收费站。本节把 uni.request 收进一个带拦截器的封装层:token 注入、域名环境切换、错误码归一、加载态与超时重试,全部收口在管道里。读完你应当能让业务代码里的每次请求短到只有一行。
封装的动机来自真实故障。商城早期代码里散落着几十处 uni.request 直调,事故清单:其一,token 过期后的行为不一——有的页面跳登录、有的只报错、有的静默失败;其二,错误提示 Triple 弹窗——列表页三个并发请求同时失败,用户连点三次"确定";其三,环境切换靠全局替换——测试环境切生产,改了几十处硬编码域名。三宗罪的共同解药是把这些横切逻辑从业务代码里请出去,收进一层管道。
封装层分三段。第一段环境与基础配置:域名按环境常量切换,超时给全局默认;第二段请求拦截:注入 token、注入公共参数;第三段响应归一:把各端各接口的成功失败翻译成统一的形状。完整骨架:
// utils/request.js const ENV = { dev: 'https://dev-api.shop.com', prod: 'https://api.shop.com' }; const BASE = ENV.prod; // 构建时按环境替换 function getToken() { return uni.getStorageSync('shop_token') || ''; } export function request(options) { const { url, method = 'GET', data = {}, silent = false } = options; return new Promise((resolve, reject) => { uni.request({ url: BASE + url, method, data, timeout: 10000, header: { Authorization: 'Bearer ' + getToken(), // 拦截:统一注入 token 'X-Client': 'uniapp' }, success: (res) => { if (res.statusCode === 401) { // 归一:登录态过期全端一个行为 uni.removeStorageSync('shop_token'); uni.reLaunch({ url: '/pages/user/login' }); return reject(new Error('unauthorized')); } if (res.statusCode >= 400) { !silent && uni.showToast({ title: '服务开小差了', icon: 'none' }); return reject(new Error('http ' + res.statusCode)); } const body = res.data; if (body.code !== 0) { !silent && uni.showToast({ title: body.msg || '请求失败', icon: 'none' }); return reject(body); } resolve(body.data); }, fail: (err) => { !silent && uni.showToast({ title: '网络不给力', icon: 'none' }); reject(err); } }); }); }
业务侧从此只剩一行:const list = await request({ url: '/goods/list', data: { page: 1 } })。三个设计决定值得展开。silent 参数让轮询、埋点这类"失败不必打扰用户"的调用关掉提示,弹窗风暴随之消失;401 统一跳登录并清态,杜绝各页面自作主张;resolve 的是 body.data 而非完整响应——业务关心的是数据不是信封,把拆信封的活也留在管道里。

管道立好后剩三件常被问的事。文件上传走 uni.uploadFile,它不共享 uni.request 的 header 逻辑,把 token 注入再写一份或抽公共函数,上传成功后的状态码同样要在管道层检查——上传接口的错误最常被漏报。超时重试只对幂等请求开(GET 拉列表可重试,POST 下单绝不能自动重发,否则重复下单),实现上给 request 加 retry 参数,失败后延时分次重发;并发控制最典型的场景是列表页同时发四个分类请求,统一在封装层加一个"进行中计数"驱动全局 loading,计数归零才收起,比每处各管一套 loading 优雅得多——准确说,是可靠得多。
// 幂等 GET 的安全重试 export function get(url, data, retry = 1) { return request({ url, data }).catch((err) => { if (retry > 0) { return new Promise((r) => setTimeout(r, 800)).then(() => get(url, data, retry - 1)); } throw err; }); }
请求类联调问题按清单走最快。域名:当前环境的 BASE 与后台登记的域名白名单是否一致,小程序端尤其要核对三类(request、上传下载、socket)。证书:https 证书链是否完整,部分安卓机型对证书链不全的服务器直接失败,浏览器却一切正常,容易误判为"手机问题"。超时:开发环境接口慢是常态,全局超时别设太短,联调期可以放宽、压测前收紧。抓包:真机抓包要装证书并允许代理,H5 端直接看开发者工具的网络面板。清单四条过完,八成的"接口不通"都能当场定位,剩下的两成带着日志找后端,对话效率也高。
请求管道收口,下一节处理最粘人的数据通道:WebSocket 长连接的保活与状态机。