小程序的请求层如果不统一封装,项目过两个月就是灾难现场:每个页面各自写 wx.request,token 忘了带、登录失效提示弹三遍、loading 转圈转一天。job-uniapp 的请求封装是在踩过一轮坑之后重写的,核心就三件事:token 注入、401 统一跳登录、loading 计数合并。
请求封装:一个 request.js 管全部
uni.request 的 Promise 化封装,把公共逻辑收进来:
// utils/request.jsconstBASE_URL='https://zpapi2.qinkonglan.cn/qkl-boot'letloadingCount=0functionshowLoading(){if(loadingCount===0)uni.showLoading({title:'加载中',mask:true})loadingCount++}functionhideLoading(){loadingCount=Math.max(0,loadingCount-1)if(loadingCount===0)uni.hideLoading()}exportfunctionrequest(options){showLoading()returnnewPromise((resolve,reject)=>{uni.request({url:BASE_URL+options.url,method:options.method||'GET',data:options.data||{},header:{'Content-Type':'application/json','Authorization':uni.getStorageSync('token')||''},success:(res)=>{hideLoading()constbody=res.dataif(body&&body.code===0){resolve(body.data)}elseif(body&&body.code===401){handleUnauthorized()reject(newError('登录已失效'))}else{uni.showToast({title:body?body.msg:'请求失败',icon:'none'})reject(newError(body?body.msg:'error'))}},fail:(err)=>{hideLoading()uni.showToast({title:'网络异常,请重试',icon:'none'})reject(err)}})})}loading 计数是关键。两个接口同时发起,一个先返回一个后返回,如果各自调 showLoading/hideLoading,第二个 hide 会把第一个的 loading 也关掉——页面还在等数据,转圈没了。计数到 0 才真正 hide,这就是"合并"。
401 跳登录:先清理再跳,别弹两遍
后端约定 code=401 表示登录态失效。处理逻辑要干净:清掉本地 token、回登录页、提示一次:
letredirecting=falsefunctionhandleUnauthorized(){if(redirecting)return// 防止多个接口同时 401 重复跳转redirecting=trueuni.removeStorageSync('token')uni.removeStorageSync('userInfo')uni.showToast({title:'登录已失效,请重新登录',icon:'none'})setTimeout(()=>{uni.reLaunch({url:'/pages/login/index'})redirecting=false},1000)}redirecting这个标志位是后来加的。最初没有它,一次会话里三个接口同时 401,页面被 reLaunch 三次,登录页闪三下,用户体验直接崩。加个锁,只跳一次。
踩坑实录:token 过期后,请求队列集体 401,登录页来回横跳
现象:用户挂着小程序看职位列表,token 悄悄过期了。下拉刷新触发 5 个接口并发,结果登录页闪了 5 次,闪完回到列表页数据还是空的,再点任意操作又跳登录——整个页面像在跳踢踏舞。
排查过程:看控制台日志,5 个接口全部返回 code=401,每个 success 分支都调了 handleUnauthorized,每次都有 1 秒的 reLaunch 延迟。时序上是:第 1 个 401 → reLaunch 登录页 → 第 2 个 401 的 setTimeout 还没执行完 → reLaunch 又触发 → 页面栈被反复重建。
定位思路:问题根源是"每个 401 都独立处理",没有全局的去重。另外 reLaunch 会关闭所有页面,正在等响应的 Promise 全部 reject,页面 onUnload 里的清理逻辑又触发一次请求,形成二次风暴。
最终解决:加redirecting标志位锁 + 把"已跳转"状态提升为模块级变量,页面 onUnload 里不再发请求。再配合请求层对 code=401 的统一拒绝:只要进入 handleUnauthorized,后续所有请求直接短路返回,不再打后端。
拦截器里还能顺手干的事
请求封装稳定后,业务码的处理逻辑也被收进来了。零工平台的错误码是后端统一约定的:code=0成功、401登录失效、403无权限、5xx服务异常、1001/1002这类业务码(岗位已满、重复报名)。前端在拦截器里按码分流,页面代码不用每个接口都写一遍 if:
// 业务码统一提示,页面不再各自弹 toastif(body.code!==0){constmsgMap={1001:'该岗位报名人数已满',1002:'你今天已经报名过了',403:'没有操作权限',}consttip=msgMap[body.code]||body.msg||'操作失败'uni.showToast({title:tip,icon:'none'})reject(newError(tip))}文件上传单独走uploadFile封装,逻辑类似但入参是本地路径、header 里不加 JSON 头——这个细节踩过坑,加了Content-Type: application/json的文件上传会 400。统一封装最大的收益是后端错误码口径变了,前端只改一处,不用满项目翻页面。
可以直接抄的清单
- loading 用计数合并:计数 >0 才 show,归 0 才 hide,防止并发请求互相关掉转圈
- 401 处理用模块级标志位锁住,一次会话只 reLaunch 一次登录页
- token 统一从
uni.getStorageSync('token')读,写请求头在 request 内部做,业务代码不感知 - 后端响应约定死:
code=0 成功 / code=401 登录失效 / 其他业务错误,前端只认这个 - 页面 onUnload 里不要发请求,避免跳转过程中的二次请求风暴
项目源码:https://gitee.com/gzqkl/job-uniapp