If you are familiar with C# programming language, to cancel a Task in C#, we issue a call to Cancel()
method call on the CancellationTokenSource
.
Axios has a similar mechanism like C# to cancel an ongoing request.
Use Case
For the purpose of this article, let’s say we will have two buttons on our page.
- Send Request: To get users from an API.
- Cancel Request: To cancel the ongoing request.
And a label to show the status of the current request.
How to setup cancellation for a request using axios
There are a couple of ways to set up cancellation in axios.
1. Passing the cancel token from CancelToken.source factory
let axiosSource = axios.CancelToken.source(); axios.get("https://reqres.in/api/users", { cancelToken: axiosSource.token, }) .then((res) => { this.users = res.data.data; });
We can setup cancelling a request by getting the cancel token from CancelToken.source()
factory method and passing the token to the axios API.
Once we set up the cancellation, we can cancel the request by invoking cancel()
function on the axiosSource.
axiosSource.cancel();
2. By passing an executor function to the cancelToken constructor
let CancelToken = axios.CancelToken; let cancel; axios.get('https://reqres.in/api/users', { cancelToken: new CancelToken(function executor(c) { cancel = c; // c is the cancel function }) }); cancel(); // cancel the request
Let’s see cancellation in action within our use case
When we click on the send request button, we can get the user’s data to show it in the UI with the following code.
this.updateState(states.IN_PROGRESS); let axiosSource = axios.CancelToken.source(); this.request = { cancel: axiosSource.cancel }; axios .get("https://reqres.in/api/users?delay=2", { cancelToken: axiosSource.token, }) .then((res) => { this.users = res.data.data; this.updateState(states.SUCCEEDED); });
In the reqres.in API, we can customize the delay so that we will have the ability to cancel the request before the data arrives.
In the above code, observe that we have a request
variable to capture the cancel function (ln 3). We can use this to issue a cancel request on the current request when the user clicks on the cancel button.
And once we have the data, we will set the data to the user’s array and update the current state using updateState
function.
Here is the full user component with bootstrap-vue buttons and table in the template section.
// User.vue <template> <div> <div> <b-button variant="outline-success" @click="send">Send Request</b-button> <b-button variant="outline-danger" :disabled="!requestInProgress" @click="cancelRequest" >Cancel Request</b-button > <b-button variant="outline-info" @click="reset">Reset</b-button> </div> <br /> <p>Request Status: {{ currentState }}</p> <div v-if="users.length > 0"> <div> <h4>Users in the reqres.in</h4> </div> <b-table striped bordered responsive hover :items="users" :fields="fields" > <template #cell(avatar)="data"> <img v-bind:src="data.value" :width="50" :height="50" /> </template> </b-table> </div> </div> </template> <script> import { BTable, BButton } from "bootstrap-vue"; import axios from "axios"; const states = { IDLE: "Idle 🟡", IN_PROGRESS: "In Progress 📀", SUCCEEDED: "Successful ✅", CANCELLED: "Cancelled ❌", }; const API_URL = "https://reqres.in/api/users?delay=2"; export default { name: "User", components: { BTable, BButton, }, data() { return { fields: ["id", "avatar", "first_name", "last_name", "email"], users: [], request: null, currentState: states.IDLE, }; }, methods: { cancelRequest() { this.cancel(); this.updateState(states.CANCELLED); }, send() { this.users = []; this.updateState(states.IN_PROGRESS); this.cancel(); let axiosSource = axios.CancelToken.source(); this.request = { cancel: axiosSource.cancel }; axios .get(API_URL, { cancelToken: axiosSource.token, }) .then((res) => { this.users = res.data.data; this.updateState(states.SUCCEEDED); }); }, updateState(msg) { this.currentState = msg; this.request = null; }, reset() { this.currentState = states.IDLE; this.cancel(); this.request = null; this.users = []; }, cancel() { if (this.request) this.request.cancel(); }, }, computed: { requestInProgress() { return this.request; }, }, }; </script>
And here is the demo of it.
If you know how to do it with Vuex you can skip the next section. Thanks for reading! [icon name=”hand-peace” prefix=”far”]
Cancelling an axios request using Vuex
Let’s see what goes into the Vuex state
- User data (stores data fetched from API)
- Status updates (Idle, In progress, success, cancelled)
- CancelRequest (stores the cancel function for cancelling the request)
So, here is our module.js
file with the initial state setup
const states = { IDLE: "Idle 🟡", IN_PROGRESS: "In Progress 📀", SUCCEEDED: "Successful ✅", CANCELLED: "Cancelled ❌", }; const state = { cancelRequest: null, users: [], currentStatus: states.IDLE }; const getters = { requestInProgress (state) { if (state.cancelRequest) { return true; } return false; } };
Let’s see what goes into our mutations.
const mutations = { addRequest (state, payload) { state.users = []; state.cancelRequest = payload; updateStatus(state, states.IN_PROGRESS); }, cancelRequest (state) { if (state.cancelRequest) { state.cancelRequest(); } state.cancelRequest = null; updateStatus(state, states.CANCELLED); }, updateUserData (state, payload) { state.users = []; payload.data.forEach(user => { state.users.push(user); }); state.cancelRequest = null; updateStatus(state, states.SUCCEEDED); } }; function updateStatus (state, msg) { state.currentStatus = msg; }
- addRequest: The cancel function we got from the cancellation token source is stored using this mutation.
- cancelRequest: This mutation will be used in the UI to cancel the ongoing network request.
- updateUserData: This will be used in the actions.js file to commit the data we got from the API call.
And here is our users component invoking the mutations.
<template> <div> <div> <b-button variant="outline-success" @click="getUsersAsync">Send Request</b-button> <b-button variant="outline-danger" :disabled="!requestInProgress" @click="cancelRequest" >Cancel Request</b-button > </div> <br /> <p>Request Status: {{ currentStatus }}</p> <div v-if="users.length > 0"> <div> <h4>Users in the reqres.in</h4> </div> <b-table striped bordered responsive hover :items="users" :fields="fields" > <template #cell(avatar)="data"> <img v-bind:src="data.value" :width="50" :height="50" /> </template> </b-table> </div> </div> </template> <script> import { BTable, BButton } from "bootstrap-vue"; import { mapActions, mapGetters, mapMutations, mapState } from "vuex"; export default { name: "Users", components: { BTable, BButton, }, data() { return { fields: ["id", "avatar", "first_name", "last_name", "email"], }; }, methods: { ...mapActions("user", ["getUsersAsync"]), ...mapMutations("user", ["cancelRequest"]) }, computed: { ...mapState("user", ["users", "currentStatus"]), ...mapGetters("user", ["requestInProgress"]) }, }; </script>
So, in the above user component, we don’t have much to read in the methods/computed listeners as we moved all the operations to mutations.
When we click on send request button we will call getUsersAsync
action. Clicking the cancel request button will call cancelRequest
mutation.
Here is our actions.js
file that makes request to user service.
// actions.js import userService from '../../services/userService'; export const getUsersAsync = async ({ commit }) => { let response = await userService.getUsersAsync(); commit('updateUserData', response.data); };
I wrote the axios call in separate userService.js
file and here it is.
// userService.js import axios from 'axios'; import userStore from '../store'; export default { async getUsersAsync () { const source = axios.CancelToken.source(); userStore.commit('user/addRequest', source.cancel); return axios({ method: 'get', url: 'https://reqres.in/api/users?delay=2', cancelToken: source.token }); } }
Here we will get the source and commit our cancel function to our user store (that’s why we import userStore
). Now, we should have our cancel function stored in the Vuex state.
That’s it! Here is the complete source code.
How to cancel multiple requests at once?
I saw this note in the axios documentation. But, I haven’t tried yet.
Note: you can cancel several requests with the same cancel token.
from https://axios-http.com/docs/cancellation
So, if we have multiple requests going at once and if we want to cancel all of them sometime later, then we can create a token (from CancelToken.source()
) and pass it to all the requests.
And if we invoke cancel()
then all the requests to which we have set the token will be cancelled at once.
References
Karthik is a passionate Full Stack developer working primarily on .NET Core, microservices, distributed systems, VUE and JavaScript. He also loves NBA basketball so you might find some NBA examples in his posts and he owns this blog.
Pingback: Dew Drop – May 28, 2021 (#3453) – Morning Dew by Alvin Ashcraft
Pingback: Dew Drop – May 28, 2021 (#3453) - Software Mile.com
Pingback: Cancelling a request issues a cancellation on backend? - Code Rethinked
Pingback: Cancelling a request issues a cancellation on backend? - Code Rethinked - News WWC