Why is it Necessary to “Cancel” HTTP Requests?
Many front-end developers have had this experience: quickly typing multiple keywords in a search box, switching pages, or loading while scrolling, old requests have not returned, and new requests have been sent out.
The result is:
- Page display is chaotic (old data overwrites new data)
- The browser initiates hundreds or thousands of requests
- Backend pressure increases significantly
- User experience declines
These are typicalredundant requests. The significance of “canceling requests” is to bring the browser, network, and logic layerback under control.
Scenario Example: Search Input Triggers 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 was 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 image, streaming, and other resource requests
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 abort 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 abort 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 will throw 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 leaves 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 abort the request.<span>error.message === 'canceled'</span>Axios’s error message will carry<span>"canceled"</span>, which is used to distinguish between active aborts 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: requests are still ongoing after the component is unmounted. At this point, trying to <span>setState</span> or modify response data 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() // Abort request when the 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 avoids wasting old requests.
Combining both can greatly optimize experiences such as search and infinite scrolling.
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 isone of the hallmarks of high-quality front-end development. It reflects a comprehensive control over performance, user experience, and system load.