axios uses promise to refresh the token and retry the failed interface

Posted by alpha2zee on Thu, 04 Nov 2021 13:28:23 +0100

In project development, we often encounter the problem of token failure and unable to request data when requesting data. In case of such problems, the general processing method is to jump to the login page and log in again to obtain the token. If you ignore the user's mood, it's not impossible to deal with it in this way.
But this is not the effect I want. As an ideal front-end, we should have higher requirements. I expect to re request a valid token when the interface expires, and use the new token to retry the last failed request.

Solution ideas

This article takes axios as an example, so we need to write on the request interceptor axios.interceptors.response.use(). My backend does not return the status code in the interface, so I do it in the error handler of the interceptor

Specific ideas:

  • Judge whether the status code (error.request.status) is 401 in the error processing function of axios.interceptors.response.use() interceptor
  • If 401 records the config = error.config of the current request and return s a request for updatetoken (the request to get a new token)
  • In order to avoid new requests coming in during the process of requesting a new token and re requesting a token again, resulting in the invalidation of a new token, we need a tag variable to lock it to ensure that new tokens will not be affected by multiple requests.
  • Set a queue to store all requests before the token is updated and completed in the form of a function to be executed
  • Update the cached token in the updateToken callback function, and update the token in the config to be retried. At the same time, in order to avoid the impact of the cache, add a timestamp parameter to the requested address
  • Enable the function to be retried in the queue. After enabling, the queue will be empty. Retry the current config instance

The token in the project exists in localStorage. The basic result of request.js is as follows:

/**
 * Method statement,
 * setToken() Method of storing token in localStorage
 * getToken() Method for obtaining token in localStorage
 * removeToken() Method of clearing token in localStorage
 * updateToken()  Request to get a new token
 *  */
 
// 1. Declaration examples
const service = axios.create({
  timeout: 50000 // request timeout
})
// Are you refreshing your tags
let isRefreshing = false
// Retry Queue 
let requests = []

service.interceptors.response.use(
  (response) => {   
    return response 
  },
  (error) => {
    if (error.request.status === 401) {
      // Record instance
      const config = error.config
      // Judge whether the token is being updated 
      if(!isRefreshing) {
      // Change tag status
        isRefreshing = true
        // You must return before requesting a token function, otherwise you can retry the successful data,
        // It will not be returned to the response of the request data function
        return updateToken().then(res => {
          setToken(res.data.access_token)
          // Retry the request stored in the queue
          requests.forEach(cb => cb())
          // Empty queue
          requests = []
          return service(config)
        }).catch(()=> {
        	// Failed to request a new token. Jump to the login page
        }).finally(() => {
        	// After the token processing is completed, the token status must be changed, otherwise the token will be re requested all the time 
          isRefreshing = false
        })
      } else {
        return new Promise((resolve) => {
          // Put the resolve into the queue, save it in a function form, and execute it directly after the token is refreshed
          requests.push(() => {
          // //Add a timestamp to the url to avoid the impact of request caching
            if( config.url.indexOf('?') > -1) {
              config.url = config.url + '&n='+ new Date().getTime()
            } else  {
              config.url = config.url + '?n='+ new Date().getTime()
            }
            config.headers['Authorization'] = getToken()
            resolve(service(config))
          })
        })
      } 
    } else {
      return Promise.reject(error)
    }
  })
export default service

Implementation reference: How axios uses promise to refresh token s painlessly

Topics: Javascript Front-end ECMAScript