Building requests with axios.js which include cancellation tokens is quite straight forward. You create a token and pass it in along with any other parameters. But adding tokens to each request and then assigning them globally somewhere and keeping track of them is awkward, especially when dealing with SPA built with Vue and Vue Router.
In my use case I needed to be able to create cancellation tokens for each request and then if a user changed page I wanted to ensure that any pending requests were immediately cancelled. Not only was this necessary for the page loading speeds, but it ensured that any state (managed with vuex) didn’t mess up.
So in short I needed to…
- Ensure that each axios call made in the application was given a cancellation token.
- The cancellation tokens were stored somewhere accessible
- Have a mechanism that meant all pending requests were terminated on specific event (page change)
- Remove any old tokens from completed requests.
Since I was using Laravel as my backend and Vue/Vue Router and Vuex on the client side, the code I used to solve this issue was held in 3 files (although the make up of your application may be slightly different the code will work the same).
The files in question are:
- store.js (manage vuex state)
- router.js (setup app routes and page nav guards)
- app.js (Setup vue instance and config)
// store.js
const store = new Vuex.Store({
strict: true,
state: {
cancelTokens: [],
},
getters: {
cancelTokens(state) {
return state.cancelTokens;
}
},
mutations: {
ADD_CANCEL_TOKEN(state, token) {
state.cancelTokens.push(token);
},
CLEAR_CANCEL_TOKENS(state) {
state.cancelTokens = [];
}
},
actions: {
CANCEL_PENDING_REQUESTS(context) {
// Cancel all request where a token exists
context.state.cancelTokens.forEach((request, i) => {
if(request.cancel){
request.cancel();
}
});
// Reset the cancelTokens store
context.commit('CLEAR_CANCEL_TOKENS');
}
}
});
// router.js
// ... router setup code omitted
// Ensures all pending requests are cancelled on route change
router.beforeEach((to, from, next) => {
store.dispatch('CANCEL_PENDING_REQUESTS');
next();
})
// app.js
// ... other setup code omitted
// Cancel Token Request Interceptor
axios.interceptors.request.use(function (config) {
// Generate cancel token source
let source = axios.CancelToken.source();
// Set cancel token on this request
config.cancelToken = source.token;
// Add to vuex to make cancellation available from anywhere
store.commit('ADD_CANCEL_TOKEN', source);
return config;
}, function (error) {
return Promise.reject(error);
});
So whats going on?
Firstly in the store.js file – cancelTokens array is created as a Vuex state variable. This will hold all created axios cancel tokens. The tokens are created via the ADD_CANCEL_TOKEN mutation. The action CANCEL_PENDING_REQUESTS when dispatched will go through the cancelTokens array and cancel all pending requests before emptying the array via CLEAR_CANCEL_TOKENS mutation. So this file manages all the cancel tokens state.
Next in the router.js file a nav guard beforeEach block ensures that whenever the vue route is changed the CANCEL_PENDING_REQUESTS action from the vuex file is called and any pending requests get cancelled.
Finally in the app.js file a global axios request interceptor makes sure that for any axios request made inside the application a cancel token is used. The cancel token source object is then stored in vuex to be accessed at any time throughout the application.