4
votes

In my Vue app, I have a response interceptor:

axios.interceptors.response.use(function (config) {
        return config;
    }, error => {
        if (error.response.status !== 401) {
            return new Promise((resolve, reject) => {
                reject(error);
            });
        }

        if (error.response.status === 401 && error.response.data.message === 'Token Expired') {
            this.store.dispatch('auth/refreshToken').then(aToken => {
                var config = error.config;
                axios.defaults.headers.common['Authorization'] = 'Bearer ' + aToken;

                return new Promise((resolve, reject) => {
                    axios.request(config).then(response => {
                        console.log(response);
                        resolve(response);
                    }).catch((error) => {
                        reject(error);
                    });
                });
            });
        }
    });

...where I am refreshing the token and making the last (intercepted) request again with the new token.

But the problem is, say I have a component, Product.vue, where I am making a request to the /products endpoint when the component mounts. I am storing all the products in a products data variable of that component. Imagine the user is in the /dashboard route now. He went for a sip of coffee and by the time he comes back the token is already expired. He visits the /products route, the response is 401, so the interceptor intercepts that response, updates the token, and then attempts to make the last failed request with the new token.

But this new request was not performed from the Product component. It was performed from the interceptor, where I don't have access to the Product component. Hence, although the request was successful, my response data are lost and the end-user will see nothing in the view because the products variable is empty.

Is there a way to track which component performed the request and remount it in the interceptor? I tried $router.push('/products') but vue throws an exception stating that navigation to current route not allowed.

Or, is there some way to handle the promise that is being returned from the interceptor in the Product component?

1

1 Answers

3
votes

All you need to do is keep the promise chain running. At the moment you're losing it by not returning the dispatch promise.

axios.interceptors.response.use(success => success, error => {
  if (error.response.status === 401 && error.response.data.message === 'Token Expired') {
    // return the new promise
    // not sure what "this" is or why "store" is in it but hey, it's your code
    return this.store.dispatch('auth/refreshToken').then(token => {
      axios.defaults.headers.common.Authorization = `Bearer ${token}`
      return axios.request(error.config)            
    })
  }
  return Promise.reject(error)
})

To explain, Axios' response interceptors insert themselves into the promise chain created by requests. Think of them like adding an extra .then() onto the end of the request before your calling code receives the data...

return axios.request({ ... })
  .then(successInterceptor)
  .catch(errorInterceptor)

In your code, you don't care about successful responses so you just return the original response

success => success

Your error interceptor then checks for expired token responses and returns a new promise that starts with renewing the token then repeating the original request (see Promise chaining).

If the response error is not for an expired token, you just maintain the error state by returning a rejected promise. If you don't do this, the rejected promise becomes a successful one which would be very confusing to your calling code 🙂.