15

I have an request interceptor for axios calls. It checks my jwt token and call for refresh if necessary.

axios.interceptors.request.use((config) =>{

    const state = store.getState(); // get renewed state
    const time = Math.floor( new Date().getTime() / 1000 );

    if( 
        ! state.app.jwtRefreshOnRequest 
        && time >= state.jwt.expires - 120
        && state.jwt.refresh_before > time
    ){ // expiring in 2 min. refresh    

        //dispatch({type: 'JWT_REFRESH_REQUEST'});
        axios.get( API_BASE_URL + '/auth/refresh')
            .then(function(response){
                // dispatch({type: 'JWT_REFRESH_SUCCESS', payload: response.data});
                axios(config).then(resolve, reject);
            })
            .catch(function(err){               
                reject(err);
        });

    }       

    return config;
}); 

This code calls the refresh correctly and saves the new token but the original call doesn't holds until the interceptor request is done, so the expired token is used.

So, I guess I need to make synchronous call from interceptor.

Sisir
  • 2,668
  • 6
  • 47
  • 82

1 Answers1

32

Avoid synchronous calls for HTTP requests, as they just make your application hang.

What you need to do here is make the calling code asynchronous - the general rule with anything callback, promise or async related is that once you are async everything needs to be async.

Here, axios.get returns a Promise - an object that keeps track of the asynchronous HTTP request and resolves once it has finished. You need to return that, rather than the config.

We do that by returning a new Promise - if an HTTP request for a new token is required it waits for it, if no it can resolve immediately.

axios.interceptors.request.use(config =>
    new Promise((resolve, reject) => {
        // ... your code ...

        axios.get( API_BASE_URL + '/auth/refresh')
            .then(response => {
                // Get your config from the response
                const newConfig = getConfigFromResponse(response);

                // Resolve the promise
                resolve(newConfig);
            }, reject);

        // Or when you don't need an HTTP request just resolve
        resolve(config);
    })
}); 

Whenever you see that then you're dealing with Promise, and once you are everything needs to return a Promise.

This is much easier if you can use async/await - new keywords supported by modern browsers and transpilable if you need to support legacy users. With these you can just put the Promise call inline with the await keyword.

axios.interceptors.request.use(async config =>

    // ... your code ...

    if(/* We need to get the async token */) {
        const response = await axios.get( API_BASE_URL + '/auth/refresh');
        config = getConfigFromResponse(response);
    }

    return config;
}); 
Keith
  • 150,284
  • 78
  • 298
  • 434
  • its not clear what getConfigFromResponse(response) does?? – ace Oct 09 '18 at 10:48
  • @ace It turns the `response` object into the `config` format - it wasn't part of the problem so the implementation is up to you. It could be `await response.json()` assuming that the status is ok and you add an outer `await`. In this answer it's just a placeholder. – Keith Oct 09 '18 at 12:44