Front-End — Simple Request Cancellation

Huda Prasetyo
GITS Apps Insight
Published in
21 min readAug 24, 2023

Improve and Save client API requests with AbortController and Cancel Token.

Make Front-End Request Cancellation with React & Vue

The Requirement for Following This Article

  1. Very basic Typescript;
  2. Axios (Promise Base HTTP Request)
  3. Fetch API (Optional, only works with AbortController)
  4. Basic React
  5. Basic Vue

Tools That You Must Have to Follow This Article

1. NodeJS
The important thing is NodeJS itself because NodeJS comes with NPM, which in this case we are going to use for installing React / Vue. You can download it here (NodeJS). I recommend installing the LTS version, I currently used v18.17.0 at the time of writing this article.

2. Text Editor
There are a bunch of Text editors out there, I prefer using VSCode which you can download here (VSCode).
It’s fine if you want to use text editors like Sublime Text or Jetbrains, just choose tools that match you.

Reasons Why This Should Be Recommended to Be Implemented

Before we go into action, let’s have a chit-chat. Here are some common reasons why we must implement this request saver with Axios Cancellation, I made some examples using Vue with version 3 (Vue3). But, I will also include how to use it inside React.

1. Are You Noticing Bloated API Requests?
Are you noticing some API requests that come from a user are spamming inside network tabs? Here are some examples that I made.

Example of user spamming request to a server

Let’s take an example if there’s some button inside the app and some user clicks that, the user will make a request to a server, and the user spamming that clicks because the user thinks “Maybe it will be a lot faster if I click button more than once”. Or maybe the user thinks that button is just like a lift button that will speed up the lift.

In the end, the user will get the same result depending on the internet speed. Because of user spam that clicks, users will get a lot of requests that are sent to the server and it makes the server bloated with the same request.

But in other cases, “My app is using a loader, when every time some user clicks some button and that button makes a request to a server, the button will be in disabled or loading state”. Okay, that’s a valid argument. But here, we think the worst-case scenario is where users like to spam some requests to a server. It will make sense if you go through reason number two below.

2. Are You Having Some Data Fetching When the User First Time Came to Some Page?
Let me explain, you have a single-page app that uses React or maybe Vue and has multiple routes inside of it. In the common case, you will have some data fetching when some users come to some routes right? Oh okay, I like to go to page one, and page one is getting data from a server, and so on for the other pages. So, where’s the problem? Here, let me show you this video that I made.

Example of some user moving between routes

See the problem? If I stay inside in some routes, let’s just say I’m going from /posts to /todos, I’m still fetching /posts data from the server. Imagine, if you have a big app, and some APIs are fetching big data, for example, 10mb per user fetch. Does that concern you? If the user exploits that method as the above video shows, your client will have a bloated data request, and your server will get a request that the client didn’t consume, that’s wasting resources and makes client and server lag.

Few Notes When Implementing Cancellation with Axios

Okay, let’s start to implement request cancellation with Axios, before we start, there are some important notes for implementing this method. Axios is offering two ways of canceling the client request:

  1. AbortController (Axios Above v0.22.0)
    AbortController is an interface that represents a controller object that allows you to abort one or more Web requests as and when desired. The full documentation can be read here.
  2. Cancel Token (Deprecated in v0.22.0)
    Cancel Token is a built-in API that came within Axios itself. The purpose of this API is the same as AbortController, but has been deprecated, and recommended to use AbortController. But don’t worry, if you have a project that uses the Axios version below v0.22.0, we still can cancel some requests in the client.

Basic Setup

Here we are going to do basic setups, such as setup plugins or hooks that we are going to use for canceling requests. Let’s start to write some code! I will be creating Request Cancellation with Axios using React and Vue, The code will be explained separately according to library and framework, but the core functionality is the same.

1. Project Structure

These are folder structures that I used to make this application, this is just some basic code for testing our application;

.
└── src
├── features
│ ├── app
│ │ ├── hooks
│ │ │ └── request-saver
│ │ │ ├── index.ts
│ │ │ └── types.ts
│ │ └── views
│ │ └── index
│ ├── post
│ │ └── views
│ │ └── index
│ └── todo
│ └── views
│ └── index
├── plugins
│ └── axios
│ └── index.ts
├── style.css

Let me iterate that folder structure:

  • features are the folder for grouping some features inside our app;
  • app is the folder for the source of the app, like reusable hooks, or maybe other reusable. In the future, will be using {feature} terminology;
  • {feature}/hooks are folders for defining reusable hooks;
  • {feature}/views are folders for defining views;
  • plugins are folders for defining third parties;
  • plugins/axios is the folder for defining third-party axios.

Another file/folder is defined when the project is generated. If you want to make it as I do like that folder structure, that’s fine. Or if you want to make your structure, that’s fine too.

Example If Using Vue Project:

Project Directory For Vue

Example If Using React Project:

Project Directory For React

2. Init Project

For starters, we must create an empty project, in order to do that, just simply run this command in your terminal to create an empty project, and yeah, we using Vite to init this project for performance purposes.

Go to a specific directory (Vue or React), and run this command, We are going to install packages inside of it, and also install Axios.

Note, if you don’t have yarn, you can use npm instead. But, if you want to install yarn, run this command:

npm i -g yarn

It will install yarn globally in your local machine. After that, just start generating the project with the code below;

For Vue Project:

# for creating vue project
yarn create request-cancellation-vue - template vue-ts

# go to vue project
cd request-cancellation-vue

# install packages project
yarn && yarn add axios vue-router@4

For React Project:

# for creating react project
yarn create request-cancellation-react - template react-ts

# go to react project
cd request-cancellation-react

# install packages for project
yarn && yarn add axios react-router-dom

Is up to you, If you want to use React or Vue, is the same thing, the difference it makes will be inside request cancellation hooks, entry point, and router

3. Make An Instance Of Axios Plugin

Here, go to the newly generated project folder, we are going to make an instance axios plugin inside src/plugins/axios/index.ts, which is very simple, and valid for Vue or React. Here is the code:

// Axios
import defaultAxios, { AxiosError } from 'axios'

const axios = defaultAxios.create({
baseURL: 'https://jsonplaceholder.typicode.com',
timeout: 5000
})

// Add a request interceptor
axios.interceptors.request.use(
config => {
// Do something before request is sent
return config
},
error => {
// Do something with request error
return Promise.reject(error)
}
)

// Add a response interceptor
axios.interceptors.response.use(
response => {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response
},
(error: AxiosError) => {
// Check if theres any cancellation request
if (defaultAxios.isCancel(error)) {
console.warn(
'Request canceled:',
`${error?.config?.baseURL}${error?.config?.url} ${error.message}`
)
}

// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error)
}
)

export { axios }

Let me iterate what’s going on up there:

  • We make a base of Axios, Every time the front-end wants to make a request to the server, just call this Axios file;
  • We making a base instance of this Axios to https://jsonplaceholder.typicode.com API;

The rest of the code it’s just an interceptor that is documented inside this documentation.

4. Make An Axios Cancellation Hook

Here is the main topic of this article. Here, we are going to make some hooks or reusable functions that can be used for canceling requests to the server, and it’s simple to understand.

Before we make the cancellation itself, we better make some type of definition for our hooks, it’s just a simple type definition. Just make this folder and file src/features/app/hooks/request-saver/types.ts. This is valid for Vue or React. Here is the code:

export type TRequestSaver = Record<string, AbortController>

For the base cancellation, make a folder and file in src/features/app/hooks/request-saver/index.ts, here is the code:

For Vue Project:

// Vue
import { ref, Ref } from 'vue'

// Axios
import { AxiosRequestConfig } from 'axios'

// Types
import { TRequestSaver } from './types'

const useRequestSaver = () => {
// Common State
const requestSaverList: Ref<TRequestSaver> = ref({})

/**
* @description Clear all incoming or running request
*
* @return {void} void
*/
const requestSaverAbortAll = (): void => {
if (Object.keys(requestSaverList.value).length > 0) {
// Abort incoming request
Object.values(requestSaverList.value).forEach(requestSaver => {
if (!requestSaver.signal.aborted) requestSaver.abort()
})

// Clear all request from state
requestSaverList.value = {}
}
}

/**
* @description Abort some request according identifier
*
* @param {string} id
*
* @return {void} void
*/
const requestSaverAbort = (id: string): void => {
const request = requestSaverList.value?.[id]

// Check if request exists
// Check if request is not aborted yet, if not aborted, abort it
// In the end, clear the aborted request
if (request && !request.signal.aborted) {
// Abort the request
request.abort()

// Delete aborted request
delete requestSaverList.value[id]
}
}

/**
* @description Set cancellation
*
* @param {string} id
*
* @return {AxiosRequestConfig} AxiosRequestConfig
*/
const requestSaverSetCancellation = (id: string): AxiosRequestConfig => {
// Check if theres any request with the same identifier
// Abort the request if exists
if (requestSaverList.value?.[id]) requestSaverAbort(id)

// Make instance of AbortController
const abortController = new AbortController()

// Make signal from abort controller to list
requestSaverList.value[id] = abortController

// Return saved signal that stored to state
return { signal: abortController.signal }
}

return {
requestSaverAbortAll,
requestSaverAbort,
requestSaverSetCancellation
}
}

export { useRequestSaver }

For React Project:

// React
import { useRef } from 'react'

// Axios
import { AxiosRequestConfig } from 'axios'

// Types
import { TRequestSaver } from './types'

const useRequestSaver = () => {
// Common State
const requestSaverList = useRef<TRequestSaver>({})

/**
* @description Clear all incoming or running request
*
* @return {void} void
*/
const requestSaverAbortAll = (): void => {
if (Object.keys(requestSaverList.current).length > 0) {
// Abort incoming request
Object.values(requestSaverList.current).forEach(requestSaver => {
if (!requestSaver.signal.aborted) requestSaver.abort()
})

// Clear all request from state
requestSaverList.current = {}
}
}

/**
* @description Abort some request according identifier
*
* @param {string} id
*
* @return {void} void
*/
const requestSaverAbort = (id: string): void => {
const request = requestSaverList.current?.[id]

// Check if request exists
// Check if request is not aborted yet, if not aborted, abort it
// In the end, clear the aborted request
if (request && !request.signal.aborted) {
// Abort the request
request.abort()

// Delete aborted request
delete requestSaverList.current[id]
}
}

/**
* @description Set cancellation
*
* @param {string} id
*
* @return {AxiosRequestConfig} AxiosRequestConfig
*/
const requestSaverSetCancellation = (id: string): AxiosRequestConfig => {
// Check if theres any request with the same identifier
// Abort the request if exists
if (requestSaverList.current?.[id]) requestSaverAbort(id)

// Make instance of AbortController
const abortController = new AbortController()

// Make signal from abort controller to list
requestSaverList.current[id] = abortController

// Return saved signal that stored to state
return { signal: abortController.signal }
}

return {
requestSaverAbortAll,
requestSaverAbort,
requestSaverSetCancellation
}
}

export { useRequestSaver }

The code above is self-explanatory. But, let me iterate on what’s going on up there;

  • useRequestSaver is the hooks name;
  • requestSaverList is a variable that contains requests that running for reaching out to the server, we use an object value that will look to be like this when we generate { SOME_REQUEST: abortFunction } ;
  • requestSaverAbortAll is a function that will abort all running requests in the background;
  • requestSaverAbort is a function that will abort specific requests. For example, if you have a request called FETCH_POST_LIST and you pass that constant string, this function will cancel only FETCH_POST_LIST . Notice, that the parameter id inside this function, is unique
  • requestSaverSetCancellation is a function that will generate some instances of AbortController and update the state of requestSaverList. So in the future when every time there’s some double request with the same identifier pass from an argument, it will get canceled. Notice, that the parameter id inside this function, is unique

So, the question is, how to implement it? Let’s jump to the next step!

Implementing Axios Cancellation With AbortController

Here we are going to implement axios cancellation when fetching API or when some users leave the page using AbortController.

If you want to read more about AbortController, visit this page.

Just a note, If you follow me when making folder structure in the Basic Setup section, then you should place your file like that code, if not, the code will break. But, if you have a folder structure that was created on your own, it’s okay, just change the path to avoid error in the future.

1. Setup Entry Point

In this step, we set up the app entry point to integrate the router, whether in Vue or React.

For Vue Project:

Update your Vue entry point in src/main.ts, to be like this:

// Vue
import { createApp } from 'vue'

// App Style
import './style.css'

// React Router
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'

// Entry Point
import App from './App.vue'

// Features
import AppIndex from './features/app/views/index.vue'
import PostIndex from './features/post/views/index.vue'
import TodoIndex from './features/todo/views/index.vue'

const routes: RouteRecordRaw[] = [
{ path: '/', name: 'appIndex', component: AppIndex },
{ path: '/posts', name: 'postIndex', component: PostIndex },
{ path: '/todos', name: 'todoIndex', component: TodoIndex }
]

const router = createRouter({
history: createWebHistory(),
routes
})

const app = createApp(App)

app.use(router)
app.mount('#app')

For React Project:

Update your React entry point in src/App.tsx, to be like this:

// Assets
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'

// Styles
import './App.css'

// React Router DOM
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'

// Features
import { AppIndex } from './features/app/views/index'
import { PostIndex } from './features/post/views/index'
import { TodoIndex } from './features/todo/views/index'

const App = () => {
return (
<Router>
<div>
<a href='https://vitejs.dev' target='_blank'>
<img src={viteLogo} className='logo' alt='Vite logo' />
</a>
<a href='https://react.dev' target='_blank'>
<img src={reactLogo} className='logo react' alt='React logo' />
</a>
</div>

<Routes>
<Route path='/' element={<AppIndex />} />
<Route path='/posts' element={<PostIndex />} />
<Route path='/todos' element={<TodoIndex />} />
</Routes>
</Router>
)
}

export default App

Okay, in that code, we define some routes and some views that will appear in our app. Here is some explanation about the routes that we define:

  • / is just an entry point for the app;
  • /posts page that fetches post list data from the server;
  • /todos page that fetches to-do list data from the server.

2. Setup Component Entry Point (For Vue Only, Skip if Using React)

Here, we just set up a basic entry point for the app component, update your src/App.vue file to be like this, we just remove some basic Vite generated, and replace it with router-view component from vue-router ;

<template>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src="/vite.svg" class="logo" alt="Vite logo" />
</a>
<a href="https://vuejs.org/" target="_blank">
<img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
</a>
</div>
<router-view />
</template>

<style scoped>
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.vue:hover {
filter: drop-shadow(0 0 2em #42b883aa);
}
</style>

3. Setup styles

This is just a fast section, just add this CSS to your CSS that was generated when first creating the project, the file is located in src/style.css for Vue Project or src/App.css for React Project;

.force-center {
display: flex;
align-items: center;
gap: 10px;
justify-content: center;
}

4. Setup App Feature View

Same as before, is just a simple setup for the user interface. Here is the code;

For Vue Project:

Make folder and file in src/features/app/views/index.vue

<template>
<div class="card">
<h1>Hey, you in Home page</h1>

<div class="force-center">
<button type="button" @click="$router.push({ name: 'todoIndex' })">
Go To Todo
</button>
<button type="button" @click="$router.push({ name: 'postIndex' })">
Go To Post
</button>
</div>
</div>
</template>

For React Project:

Make folder and file in src/features/app/views/index.tsx

// React Router DOM
import { useNavigate } from 'react-router-dom'

const AppIndex = () => {
// Navigation
const navigate = useNavigate()

return (
<div className='card'>
<h1>Hey, you in Home page</h1>

<div className='force-center'>
<button type='button' onClick={() => navigate('/todos')}>
Go To Todo
</button>
<button type='button' onClick={() => navigate('/posts')}>
Go To Post
</button>
</div>
</div>
)
}

export { AppIndex }

5. Setup Post Feature View

Still, it’s just a basic implementation of view that integrates some API inside of it and makes use of the useRequestSaver hooks that we created before. Here is the code:

For Vue Project:

Make a folder and file in src/features/post/views/index.vue

<script lang="ts" setup>
// Vue
import { onMounted, onBeforeUnmount } from 'vue'

// Plugins
import { axios } from '../../../plugins/axios'

// Hooks
import { useRequestSaver } from '../../app/hooks/request-saver'

// Instance of hook
const requestSaver = useRequestSaver()

/**
* @description Load post list
*
* @return {Promise<void>} Promise<void>
*/
const fetchPostList = async (): Promise<void> => {
try {
await axios.get('/posts', {
...requestSaver.requestSaverSetCancellation('FETCH_POST_LIST')
})
} catch (_) {
//
}
}

// Do when user came to this page
onMounted(() => {
fetchPostList()
})

// Clear when user came out from this page
onBeforeUnmount(() => {
requestSaver.requestSaverAbortAll()
})
</script>

<template>
<div class="card">
<h1>Hey, you in Post page</h1>

<div class="force-center">
<button type="button" @click="$router.push({ name: 'appIndex' })">
Back To Home
</button>
<button type="button" @click="$router.push({ name: 'todoIndex' })">
Go To Todo
</button>
</div>
</div>
</template>

For React Project:

Make a folder and file in src/features/post/views/index.tsx

// React
import { useState, useEffect, useCallback } from 'react'

// React Router DOM
import { useNavigate } from 'react-router'

// Axios
import { axios } from '../../../plugins/axios'

// Custom Hooks
import { useRequestSaver } from '../../app/hooks/request-saver'

const PostIndex = () => {
// Common State
const [isFirstTimeLoaded, setIsFirstTimeLoaded] = useState(true)

// Navigation
const navigate = useNavigate()

// Hooks
const requestSaver = useRequestSaver()

/**
* @description Load post list
*
* @return {Promise<void>} Promise<void>
*/
const fetchPostList = useCallback(async (): Promise<void> => {
try {
await axios.get('/posts', {
...requestSaver.requestSaverSetCancellation('FETCH_POST_LIST')
})
} catch (_) {
//
}
}, [requestSaver])

/**
* @description Fetch initial when first time came to this page
*
* @return {Promise<void>} Promise<void>
*/
const fetchInitial = useCallback(async (): Promise<void> => {
try {
await fetchPostList()
} finally {
setIsFirstTimeLoaded(false)
}
}, [fetchPostList])

// Do when user came to this page
useEffect(() => {
if (isFirstTimeLoaded) fetchInitial()

// Do when user came out from this page
return () => {
if (!isFirstTimeLoaded) requestSaver.requestSaverAbortAll()
}

// eslint-disable-next-line
}, [isFirstTimeLoaded])

return (
<div className='card'>
<h1>Hey, you in Post page</h1>

<div className='force-center'>
<button type='button' onClick={() => navigate('/')}>
Back To Home
</button>
<button type='button' onClick={() => navigate('/todos')}>
Go To Todo
</button>
</div>
</div>
)
}

export { PostIndex }

REMINDER: That path that I used, is using folder structure that I told before, if you have another folder structure, just import from the correct location.

Let me iterate that code:

  • We making a simple HTML for home feature;
  • We import onMounted and onBeforeMount hooks for Vue (if using Vue);
  • We import useEffect for React (if using React);
  • We import axios plugins;
  • We import useRequestSaver hook;
  • We make fetchPostList that contains axios for making a request to the server. Here, we see the difference when making axios request, we fill the second argument with an object that AxiosRequestConfig accepts, and we fill with requestSaver.requestSaverSetCancellation('FETCH_POST_LIST') . That means we integrate that request saver to this function in order that every time we call this function whether that’s a duplicate call, or maybe we spam this function, the request will be triggered once.
  • We call the fetchPostList when a user comes to this page;
  • And lastly, we call requestSaver.requestSaverAbortAll() to cancel running requests in the background. This is a good example if you have another API call inside this page, and when the user leaves the page, the app will cancel that request and we do not need to worry about the process that running in the background.

6. Setup Todo Feature View

Same as previous point 5. It’s just a basic implementation of view that integrates some API inside of it and makes use of the useRequestSaver hook that we created before.

For Vue Project:

Make a folder and file src/features/todo/views/index.vue and here’s the code:

<script lang="ts" setup>
// Vue
import { onMounted, onBeforeUnmount } from 'vue'

// Plugins
import { axios } from '../../../plugins/axios'

// Hooks
import { useRequestSaver } from '../../app/hooks/request-saver'

// Instance of hook
const requestSaver = useRequestSaver()

/**
* @description Load todo list
*
* @return {Promise<void>} Promise<void>
*/
const fetchTodoList = async (): Promise<void> => {
try {
await axios.get('/todos', {
...requestSaver.requestSaverSetCancellation('FETCH_TODO_LIST')
})
} catch (_) {
//
}
}

// Do when user came to this page
onMounted(() => {
fetchTodoList()
})

// Clear when user came out from this page
onBeforeUnmount(() => {
requestSaver.requestSaverAbortAll()
})
</script>

<template>
<div class="card">
<h1>Hey, you in Todo page</h1>

<div class="force-center">
<button type="button" @click="$router.push({ name: 'appIndex' })">
Back To Home
</button>
<button type="button" @click="$router.push({ name: 'postIndex' })">
Go To Post
</button>
</div>
</div>
</template>

For React Project:

Make a folder and file src/features/todo/views/index.tsx and here’s the code:

// React
import { useState, useEffect, useCallback } from 'react'

// React Router DOM
import { useNavigate } from 'react-router'

// Axios
import { axios } from '../../../plugins/axios'

// Custom Hooks
import { useRequestSaver } from '../../app/hooks/request-saver'

const TodoIndex = () => {
// Common State
const [isFirstTimeLoaded, setIsFirstTimeLoaded] = useState(true)

// Navigation
const navigate = useNavigate()

// Hooks
const requestSaver = useRequestSaver()

/**
* @description Load todo list
*
* @return {Promise<void>} Promise<void>
*/
const fetchTodoList = useCallback(async (): Promise<void> => {
try {
await axios.get('/todos', {
...requestSaver.requestSaverSetCancellation('FETCH_TODO_LIST')
})
} catch (_) {
//
}
}, [requestSaver])

/**
* @description Fetch initial when first time came to this page *
*
* @return {Promise<void>} Promise<void>
*/
const fetchInitial = useCallback(async (): Promise<void> => {
try {
await fetchTodoList()
} finally {
setIsFirstTimeLoaded(false)
}
}, [fetchTodoList])

// Do when user came to this page
useEffect(() => {
if (isFirstTimeLoaded) fetchInitial()

// Do when user came out from this page
return () => {
if (!isFirstTimeLoaded) requestSaver.requestSaverAbortAll()
}

// eslint-disable-next-line
}, [isFirstTimeLoaded])

return (
<div className='card'>
<h1>Hey, you in Todo page</h1>

<div className='force-center'>
<button type='button' onClick={() => navigate('/')}>
Back To Home
</button>
<button type='button' onClick={() => navigate('/posts')}>
Go To Post
</button>
</div>
</div>
)
}

export { TodoIndex }

7. Test It Out

To test the app, we just run this command

yarn dev

It will expose our app in localhost:5173. Open that in a browser, and finally, we just cancel any request that running in the background that the user didn’t need. Here is an example of canceling a running request in the background with Axios Cancellation.

For Vue Project:

Canceling request when route leaves

For React Project:

Canceling request when route leaves

Notes: I’m using Dev Tools Chrome in Network Tab with a Slow 3g Network. If using No Throttle and your internet is fast, the request is done quickly and cannot be canceled. For testing purposes, we using Slow 3g Network to see if some requests are pending in the background, we can see the canceled request.

Just like that, we save bandwidth, improve performance, and no more spamming requests to the server and in a client.

Implementing Axios Cancellation With Cancel Token

For Axios below v0.22.0, you can’t use AbortController. Here we are going to implement axios cancellation when fetching API or when some users leave the page using Cancel Token.

I’m going explain to the point and just share the example code of the hook, because the usage of this hook, is the same as before. I’m just changing the logic inside of it. Here is the code:

Before writing, let’s make a new folder and file in src/features/app/hooks/request-saver-cancel-token. Inside it, make a type file like src/features/app/hooks/request-saver-cancel-token/types.ts. Here’s the code:

// Axios
import { CancelTokenSource } from 'axios'

export type TRequestSaver = Record<string, CancelTokenSource>

After that, make a new folder and file again in src/features/app/hooks/request-saver-cancel-token/index.ts. Here’s the code:

For Vue Project:

// Vue
import { ref, Ref } from 'vue'

// Axios
import axios, { AxiosRequestConfig } from 'axios'

// Types
import { TRequestSaver } from './types'

const useRequestSaver = () => {
// Common State
const requestSaverList: Ref<TRequestSaver> = ref({})

/**
* @description Clear all incoming or running request
*
* @return {void} void
*/
const requestSaverAbortAll = (): void => {
if (Object.keys(requestSaverList.value).length > 0) {
// Abort incoming request
Object.values(requestSaverList.value).forEach(requestSaver => {
requestSaver.cancel()
})

// Clear all request from state
requestSaverList.value = {}
}
}

/**
* @description Abort some request according identifier
*
* @param {string} id
*
* @return {void} void
*/
const requestSaverAbort = (id: string): void => {
const request = requestSaverList.value?.[id]

// Check if request exists
// Check if request is not aborted yet, if not aborted, abort it
// In the end, clear the aborted request
if (request) {
// Abort the request
request.cancel()

// Delete aborted request
delete requestSaverList.value[id]
}
}

/**
* @description Set cancellation
*
* @param {string} id
*
* @return {AxiosRequestConfig} AxiosRequestConfig
*/
const requestSaverSetCancellation = (id: string): AxiosRequestConfig => {
// Check if theres any request with the same identifier
// Abort the request if exists
if (requestSaverList.value?.[id]) requestSaverAbort(id)

// Make instance of Axios Source
const axiosSource = axios.CancelToken.source()

// Make key value pair of cancel token to list
requestSaverList.value[id] = {
cancel: axiosSource.cancel,
token: axiosSource.token
}

// Return saved signal that stored to state
return { cancelToken: axiosSource.token }
}

return {
requestSaverAbortAll,
requestSaverAbort,
requestSaverSetCancellation
}
}

export { useRequestSaver }

For React Project:

// React
import { useRef } from 'react'

// Axios
import axios, { AxiosRequestConfig } from 'axios'

// Types
import { TRequestSaver } from './types'

const useRequestSaver = () => {
// Common State
const requestSaverList = useRef<TRequestSaver>({})

/**
* @description Clear all incoming or running request
*
* @return {void} void
*/
const requestSaverAbortAll = (): void => {
if (Object.keys(requestSaverList.current).length > 0) {
// Abort incoming request
Object.values(requestSaverList.current).forEach(requestSaver => {
requestSaver.cancel()
})

// Clear all request from state
requestSaverList.current = {}
}
}

/**
* @description Abort some request according identifier
*
* @param {string} id
*
* @return {void} void
*/
const requestSaverAbort = (id: string): void => {
const request = requestSaverList.current?.[id]

// Check if request exists
// Check if request is not aborted yet, if not aborted, abort it
// In the end, clear the aborted request
if (request) {
// Abort the request
request.cancel()

// Delete aborted request
delete requestSaverList.current[id]
}
}

/**
* @description Set cancellation
*
* @param {string} id
*
* @return {AxiosRequestConfig} AxiosRequestConfig
*/
const requestSaverSetCancellation = (id: string): AxiosRequestConfig => {
// Check if theres any request with the same identifier
// Abort the request if exists
if (requestSaverList.current?.[id]) requestSaverAbort(id)

// Make instance of Axios Source
const axiosSource = axios.CancelToken.source()

// Make key value pair of cancel token to list
requestSaverList.current[id] = {
cancel: axiosSource.cancel,
token: axiosSource.token
}

// Return saved signal that stored to state
return { cancelToken: axiosSource.token }
}

return {
requestSaverAbortAll,
requestSaverAbort,
requestSaverSetCancellation
}
}

export { useRequestSaver }

It’s just the same function, and functionality. But, I’m making some changes inside requestSaverSetCancellation , I just change AbortController to axios.cancelToken.source . Please notice, the usage is the same, just refer to this newly created file if you want to use it, it’s just a change logic inside of it, with no breaking changes.

Conclusion

Yeah, you made it through the end of the article, Congrats! After you read this article, you wanna use this method or not? Or are you gonna stick just with the loader in your app? That’s up to you. The important is, that you understand what’s is Request cancellation method and how to use it using Axios.

Or you wanna use it inside fetch API? Of course, you can, but only using the AbortController method and changing the return type of the function from the Axios package to the return type that you desired.

Kudos to Teguh Muhammad Zundi and Dyah Eka for contributing in order to make this article!

For the full documentation of Axios cancellation, you can read more here:

For the full code for this article, you can find it here:

If you have any questions, please, leave a comment. See you in the next article!

Huda Prasetyo is a Fullstack Developer at GITS.id. I’m a fullstack, but for now I’m currently focusing in Front-End Development. Couple years before at GITS, I was a Fullstack Developer using Laravel. In the meantime, time has changed, I want to try something new in the future and choose my own path as Front-End Developer using Vue and React. Still curious about me? My Linkedin is free to visit.

--

--