How to cancel a request using Axios in Vue JS

cancel request using axios

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);
  });

I’m the reqres.in API as 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.

Demo of cancelling axios request
Demo: cancelling a request with axios

If you know how to do it with Vuex you can skip the next section. Thanks for reading!

Cancelling a request with axios using Vuex

Let’s see what goes into the Vuex state

  1. User data (stores data fetched from API)
  2. Status updates (Idle, In progress, success, cancelled)
  3. 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