因为token定期过期,这时请求返回token过期,就需要拿着refreshToken去请求新的token,但是因为ajax是异步请求,所以会存在多个接口重复刷新token,引发报错。
解决办法:刷新一次token,其余请求拦截放入缓存中,等token刷新成功后在发起请求。
我是uiapp项目,以unapp为例,其余项目同理
1.封装请求函数,你的就是你自己的封装好的请求函数,在提示token异常需要重新刷新token时执行checkStatus函数,只执行第一个刷新token的请求,其余拦截,并将请求成功后的回调请求缓存

const http = (params) => {
  //返回promise 对象
  return new Promise((resolve, reject) => {
    uni.request({
      // 服务器url+参数中携带的接口具体地址
      url: Host + params.url,
      // 请求参数
      data: params.data,
      // 设置后端需要的常用的格式就好,特殊情况调用的时候单独设置
      header: params.header || {
        "Content-Type": "application/json;charset=utf-8",
        "api-version": params.apiVersion ||'1.0',
		"Authorization": uni.getStorageSync("accessToken")
      },
      method: params.method && params.method.toUpperCase() || 'POST',
      dataType: params.dataType,//返回的数据格式,默认为JSON,特殊格式可以在调用的时候传入参数
      responseType: params.responseType,//响应的数据类型
      success: res => {
        // 接口访问正常返回数据
        if (res.statusCode == 200&&res.data.code === 200) {
          //1. 操作成功返回数据
            resolve(res.data)
        } else {
          //2. 操作不成功返回数据,以toast方式弹出响应信息,如后端未格式化非操作成功异常信息,则可以统一定义异常提示
	 if(res.data.code === 401){ //token异常
			  //checkStatus 是一个promise,必须resolve返回请求数据,不然请求接口无法获取返回结果
				  checkStatus(params).then(res=>{
				  		resolve(res)
				  })
		  }else{
			  resolve(res.data)
			  if(res.data.msg){
			    uni.showToast({
			      icon: "none",
			      title: res.data.msg,
                  duration:3000
			    })
			  }
		  }
        }
      },
      fail: function (e) {
        if(e.errMsg === "request:fail timeout"){
          uni.showToast({
            icon: "none",
            title: "网络连接超时,请稍后重试"
          })
        }
        reject(e);
        setTimeout(() =>{
          uni.hideLoading();
        }, 1500)
      }
    })
  })
}

2.checkStatus函数,发送第一个刷新token请求,其余请求拦截,将刷新token后的回调请求存入缓存,写一个开关控制发送请求。

let isRefreshing = true;
function checkStatus(params) { 
			      // 刷新token的函数,这需要添加一个开关,防止重复请求
			    if(isRefreshing){
			          referToken()
			  	}
			  	isRefreshing = false; 
			  		// 将token刷新成功后的回调请求缓存
			  	const retryOriginalRequest = new Promise((resolve) => {
			  		            addSubscriber(()=> {			
			  							resolve(http(params))
			  		            })		
			  	});
			  	return retryOriginalRequest;
	}

3.token刷新函数

function referToken(){
		const params={
			  "data":{
			  		"refreshToken":uni.getStorageSync("refreshToken")
			  }
		}
		
		if(uni.getStorageSync("refreshToken")){
					  http({
					  	url:`/oauth2/refreshToken`,
					  	data:aes.encrypt(JSON.stringify(params)),
					  	header:{
					  		"Content-Type": "application/json;charset=utf-8",
					  		"api-version": params.apiVersion ||'1.0'
					  	}
					  }).then(res=>{
					  					   if(res.code===200){		     						 uni.setStorageSync("refreshToken",res.data.refreshToken)
uni.setStorageSync("accessToken",res.data.accessToken)
											   //执行缓存中的请求
											   onAccessTokenFetched()
											   //延迟几秒再将刷新token的开关放开,不然偶尔还是会重复提交刷新token的请求
					  					       setTimeout(()=>{
												   isRefreshing = true
											   },3000)
										   }else{
					  						   //跳转至登录页面
					  					   }
					  })
	

			  // })
		}
	

}

4.缓存数组

// 缓存
let subscribers = [];
function onAccessTokenFetched() {
    subscribers.forEach((callback)=>{
        callback()
    })
    subscribers = [];
}

function addSubscriber(callback) {
    subscribers.push(callback)
}

5.注意事项
第一点:将第一个刷新token发送请求,其余刷新token拦截,并将刷新token后的回调请求存入缓存中时,执行该函数是一个promise,返回的数据通过该resolve,返回给调用接口。
checkStatus(params).then(res=>{
resolve(res)
})
第二点:当刷新token接口成功后,开放可刷新token的开关延迟几秒放开,不然也可能造成刷新请求重复提交
setTimeout(()=>{
sRefreshing = true
},3000)