Why is it Necessary to “Cancel” HTTP Requests?
Many front-end developers have experienced this: when quickly typing multiple keywords in a search box, switching pages, or loading content while scrolling, the old requests have not returned, and new requests are being sent out.
The result is:
- Page display issues (old data overwriting new data)
- Browsers initiating hundreds or thousands of requests
- Increased pressure on the backend
- Decreased user experience
These are typical examples of redundant requests. The significance of “canceling requests” is to bring the browser, network, and logic layer back under control.
Scenario Example: Search Input Triggering Requests
Suppose we implement a search function that triggers a request every time the input changes:
input.addEventListener('input', e => {
fetch(`/api/search?q=${e.target.value}`)
.then(res => res.json())
.then(showResults)
})
If the user types the seven letters of “javascript” continuously, it will initiate seven requests. But we only need the last one. Therefore, we need toβ cancel the previous requests.
Method 1: Using <span>AbortController</span> (Modern Browser Standard)
<span>AbortController</span> is a natively supported abort mechanism:
let controller = null
async function search(keyword) {
// Cancel the old request before each call
if (controller) controller.abort()
controller = new AbortController()
const signal = controller.signal
try {
const res = await fetch(`/api/search?q=${keyword}`, { signal })
const data = await res.json()
showResults(data)
} catch (err) {
if (err.name === 'AbortError') {
console.log('Request canceled:', keyword)
} else {
console.error(err)
}
}
}
β Advantages:
- Natively supported, no need for third-party libraries
- Perfectly integrates with
<span>fetch</span> - Can be extended to requests for images, streaming, and other resources
Method 2: New Axios Syntax (Based on AbortController)
Starting from Axios <span>v0.22.0</span>, Axios has fully supported the native AbortController, which means we can elegantly cancel requests just like using <span>fetch</span>.
Here is a complete example of a file upload or search request:
import axios from 'axios'
// Create a global controller to cancel requests when needed
let controller = null
async function uploadFile(formData) {
// If there is an ongoing request, cancel it first
if (controller) controller.abort()
// Create a new controller
controller = new AbortController()
try {
const response = await axios.post('/api/upload', formData, {
// Pass the signal to Axios
signal: controller.signal,
})
console.log('Upload successful β
', response.data)
} catch (error) {
// When the request is canceled, Axios throws an error with message === "canceled"
if (error.message === 'canceled') {
console.log('Request has been canceled π«')
} else {
console.error('Upload failed β', error)
}
}
}
// Simulate canceling the request
setTimeout(() => {
console.log('User left the page, canceling upload...')
controller.abort()
}, 2000)
Important Knowledge Points:
<span>controller = new AbortController()</span>Each request needs an independent controller instance; otherwise, multiple requests will interfere with each other.<span>signal: controller.signal</span>Axios will listen to this signal, and when<span>controller.abort()</span>is called, it will immediately cancel the request.<span>error.message === 'canceled'</span>Axios’s error message will carry<span>"canceled"</span>, which is used to distinguish between an active cancellation and other errors.
Applicable Scenarios:
- Cancel requests when switching pages (to avoid React/Vue state update errors)
- High-frequency triggers (such as search boxes, infinite scrolling)
- User-initiated interruptions during upload/download tasks
Method 3: Handling in Frameworks (React/Vue)
The most common issue in components is that requests are still ongoing after the component is unmounted. Trying to <span>setState</span> or modify response data at this point will trigger a “memory leak” warning.
πΏ React Hook Scenario:
useEffect(() => {
const controller = new AbortController()
fetch(`/api/search?q=${query}`, { signal: controller.signal })
.then(res => res.json())
.then(setData)
.catch(err => {
if (err.name !== 'AbortError') console.error(err)
})
return () => controller.abort() // Cancel request when component unmounts
}, [query])
This prevents asynchronous updates from executing after the component unmounts, avoiding the “Can’t perform a React state update on an unmounted component” error.
π Vue Scenario (Composition API):
const controller = ref(null)
async function fetchData(keyword) {
controller.value?.abort()
controller.value = new AbortController()
const res = await fetch(`/api/search?q=${keyword}`, { signal: controller.value.signal })
data.value = await res.json()
}
Advanced: Combining Throttle and Debounce
Debounce avoids frequent requests, while Abort prevents wasting old requests.
The combination of both can greatly optimize experiences in search, infinite scrolling, etc.
const debounce = (fn, delay = 500) => {
let timer
return (...args) => {
clearTimeout(timer)
timer = setTimeout(() => fn(...args), delay)
}
}
const handleSearch = debounce(keyword => {
search(keyword)
}, 500)
With the previous cancellation logic, it can be easily implemented:
“Only send one request, always get the latest result.”
Best Practice Summary
| Scenario | Recommended Solution |
|---|---|
| Regular fetch requests | <span>AbortController</span> |
| Axios projects | <span>AbortController</span> |
| React/Vue component requests | <span>useEffect</span> / <span>onUnmounted</span> + <span>abort()</span> |
| High-frequency triggers (search box) | <span>debounce + abort()</span> |
| Concurrency control (multiple requests simultaneously) | Can be combined with <span>Promise.allSettled</span> / request pool mechanism |
Conclusion
Cancelling redundant requests may seem like a small optimization, but it is one of the hallmarks of high-quality front-end development. It reflects a comprehensive control over performance, user experience, and system load.