请求队列
在开发过程中会遇到一些非必要实时请求,比如埋点。当埋点和主接口同属一个域下时,如果埋点请求数量太多时会影响主接口的请求(同域请求上限)。故而可增加埋点请求队列,一次只能请求一个埋点。
let logQueue = []
let logStatus = 'stop'
export const log = (url, data) => {
let config = {
data,
...
}
logQueue.push(config)
if(logStatus === 'stop'){
doLog()
}
}
const doLog = () => {
let config = logQueue.pop()
logStatus = 'pending'
fetch.ajax(config).finally(() => {
if(logQueue.length){
doLog()
}else{
logStatus = 'stop'
}
})
}
优点:解决了可能存在的请求阻塞问题,用户体验提升
缺点:如果请求队列未请求完成时用户就关闭了页面,可能会导致请求丢失
刷新token机制
在实际开发过程中,我们经常会拿到后端两个token(一个长token,一个短token,长短指时间),短token用于校验用户,长token用于刷新短token。
当请求接口A时,假设短token过期,我们需要用长token来刷新短token,如果刷新成功则不需要用户重新登录,直接再次请求接口A来实现用户无感知token更新以及数据正常回显。
let isRefreshing = false
let requests = []
let request = null
request = (type, url, data = {}, hideToast) => {
const accessToken = localStorage.getItem("accessToken")
let config = {
url,
data,
type,
...
}
let curRequest = null
if(url === '/api/refresh'){
// 刷新接口单独定义处理逻辑
curRequest = fetch
.ajax(config)
.then(xhr => {
const { resp } = xhr
if (resp?.code === 200) {
const accessToken = resp?.data?.accessToken
const refreshToken = resp?.data?.refreshToken
localStorage.setItem("accessToken", accessToken)
localStorage.setItem("refreshToken", refreshToken)
config.headers.Authorization = accessToken
// 重新执行刷新token期间待执行接口
requests.forEach(cb => cb(accessToken))
} else {
!hideToast && toastContent("Login information expired.", 'error')
// 重新执行刷新token期间待执行接口
requests.forEach(cb => cb())
toLogin()
}
return xhr
})
.catch(() => {
!hideToast && toastContent("Login information expired.", 'error')
// 重新执行刷新token期间待执行接口
requests.forEach(cb => cb())
toLogin()
})
.finally(() => {
requests = []
isRefreshing = false
})
}else if(isRefreshing){
// token刷新期间,返回promise,并将该次请求存储在requests数组内
return new Promise(resolve => {
requests.push(token => {
// token刷新接口调用后,如果token有值则重新调用接口,否则抛出异常
if(token){
config.headers.Authorization = token
resolve(fetch.ajax(config).then(xhr => xhr?.resp))
}else{
resolve({code: -1001, loginError: true})
}
})
})
}else{
curRequest = fetch
.ajax(config)
.then(xhr => {
const { resp } = xhr
if(resp?.code === xxx){
requests = []
!hideToast && toastContent("Login failed.", 'error')
toLogin()
return {code: -1001, loginError: true}
}else if (resp?.code === 411) {
// token过期
const refreshToken = localStorage.getItem("refreshToken")
// 如果token不在刷新期间则调用刷新
if (!isRefreshing) {
isRefreshing = true
userService.tokenRefresh({ refreshToken, hideToast })
}
// 返回promise,并将该次请求存储在requests数组内
return new Promise(resolve => {
requests.push(token => {
// token刷新接口调用后,如果token有值则重新调用接口,否则抛出异常
if(token){
config.headers.Authorization = token
resolve(fetch.ajax(config).then(xhr => xhr?.resp))
}else{
resolve({code: -1001, loginError: true})
}
})
})
}
return resp
})
.catch(error => {
toastContent("Server Error!", 'error')
return {code: -1002, serverError: true}
})
}
curRequest.xhr = fetch.currentXHR
return curRequest
}