axios取消重复请求与更新token并续订上次请求

一、问题引入

当用户发起一个请求时,判断token是否已过期,若已过期则先调refreshToken接口,拿到新的token后再继续执行之前的请求。

难点:当同时发起多个请求,token 过期会调用多次更新 token 接口;此时刷新token的接口还没返回,此时其他请求该如何处理,在刷新token接口返回后才能续订请求

二、取消重复请求

针对同时发起多个请求,token 过期会调用多次更新 token 接口,这里只做了取消重复请求操作,未能续订之前请求

维护一个 请求列表 reqList 

在 axios 请求拦截器中判断请求是否已经在 reqList  中存在,存在则调用 axios.CancelToken 取消请求

// 正在进行中的请求列表
let reqList = []

/**
 * 阻止重复请求
 * @param {array} reqList - 请求缓存列表
 * @param {string} url - 当前请求地址
 * @param {function} cancel - 请求中断函数
 * @param {string} errorMessage - 请求中断时需要显示的错误信息
 */
const stopRepeatRequest = function (reqList, url, cancel, errorMessage) {
  const errorMsg = errorMessage || ''
  for (let i = 0; i < reqList.length; i++) {
    if (reqList[i] === url) {
      cancel(errorMsg)
      return
    }
  }
  reqList.push(url)
}

/**
 * 允许某个请求可以继续进行
 * @param {array} reqList 全部请求列表
 * @param {string} url 请求地址
 */
const allowRequest = function (reqList, url) {
  for (let i = 0; i < reqList.length; i++) {
    if (reqList[i] === url) {
      reqList.splice(i, 1)
      break
    }
  }
}

const service = axios.create()

// 请求拦截器
service.interceptors.request.use(
  config => {
 let cancel
   // 设置cancelToken对象
    config.cancelToken = new axios.CancelToken(function(c) {
     cancel = c
    })
    // 阻止重复请求。当上个请求未完成时,相同的请求不会进行
    stopRepeatRequest(reqList, config.url, cancel, `${config.url} 请求被中断`)
    return config
  },
  err => Promise.reject(err)
)

// 响应拦截器
service.interceptors.response.use(
  response => {
    // 增加延迟,相同请求不得在短时间内重复发送
    setTimeout(() => {
      allowRequest(reqList, response.config.url)
    }, 1000)
    // ...请求成功后的后续操作
    // successHandler(response)
  },
  error => {
    if (axios.isCancel(thrown)) {
      console.log(thrown.message);
    } else {
      // 增加延迟,相同请求不得在短时间内重复发送
      setTimeout(() => {
        allowRequest(reqList, error.config.url)
      }, 1000)
    }
    // ...请求失败后的后续操作
    // errorHandler(error)
  }
)

三、更新token并续订上次请求

此方法可用于多个请求token失效,进行多次更新 token 请求操作时,取消其他更新 token 请求,并在完成更新后续订上次多个请求

// 是否正在刷新的标记
let isRefreshing = false
// 重试队列,每一项将是一个待执行的函数形式
let retryRequests = []


// 请求拦截
service.interceptors.request.use(
    config => {
        const token = localStorage.getItem('access_token')
        if (token && !config.url.includes('/oauth/token')) {
            config.headers.Authorization = 'Bearer ' + token;
        }
        if (config.method === 'post' || config.method === 'put') {
            config.data = qs.stringify(config.data);
        }
        return config;
    },
    err => {
        return Promise.reject(err);
    }
);

// 响应拦截
service.interceptors.response.use(
    async response => {
        if (response.status === 200) {
            if (response.data && response.data.code == 4001) {
                const config = response.config
                if (!isRefreshing) {
                    isRefreshing = true
                    // const refresh_token = localStorage.getItem('refresh_token')
                    // const token = localStorage.getItem('access_token')
                    try {
                        const result = await getToken({
                            grant_type: 'password',
                            client_id: 'client-app',
                            client_secret: '123',
                            username: 'sunhj',
                            password: '123'
                        })
                        localStorage.setItem('access_token', result.token || '')
                        // localStorage.setItem('refresh_token', res.refreshToken || '')
                        const token = localStorage.getItem('access_token')
                        config.headers['Authorization'] = 'Bearer ' + token
                        // 已经刷新了token,将所有队列中的请求进行重试
                        retryRequests.forEach(cb => cb('Bearer ' + token))
                        // 重试完清空这个队列
                        retryRequests = []
                        // 这边不需要baseURL是因为会重新请求url,url中已经包含baseURL的部分了
                        config.baseURL = proxyHost
                        if (config.method === 'post' || config.method === 'put') {
                            config.data = qs.parse(config.data)
                        }
                        isRefreshing = false
                        return service(config)
                    } catch (error) {
                        isRefreshing = false
                    }
                } else {
                    // 正在刷新token,返回一个未执行resolve的promise
                    return new Promise((resolve) => {
                        // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
                        retryRequests.push((token) => {
                            config.baseURL = proxyHost
                            config.headers['Authorization'] = token
                            if (config.method === 'post' || config.method === 'put') {
                                config.data = qs.parse(config.data)
                            }
                            resolve(service(config))
                        })
                    })
                }
            } else {
                // 二进制流文件下载
                if (response.headers['content-type'] === "application/octet-stream;charset=UTF-8") {
                    return response;
                } else {
                    return response.data;
                }
            }
        } else {
            Promise.reject();
        }
    },
    err => {
        return Promise.reject(err);
    }
);

参考:https://blog.csdn.net/weixin_45178716/article/details/103286459

posted @ 2022-09-14 12:05  盼星星盼太阳  阅读(1303)  评论(0编辑  收藏  举报