How to Implement Request Deduplication in Axios
Request deduplication is a technique used to prevent duplicate, simultaneous HTTP requests from hitting the server by sharing a single in-flight response among identical callers. This article explains the step-by-step process of implementing request deduplication in the Axios HTTP client using a cache of pending promises and Axios interceptors.
1. Generating a Unique Request Key
To identify duplicate requests, you must create a unique identifier based on the request configuration. This key typically combines the HTTP method, the URL, and serialized versions of the query parameters and request body.
function generateRequestKey(config) {
const { method, url, params, data } = config;
return [
method?.toLowerCase(),
url,
JSON.stringify(params || {}),
JSON.stringify(data || {})
].join('&');
}2. Maintaining a Pending Requests Map
Create a Map instance to store currently in-flight
requests. This map associates the unique request key with the pending
Axios promise.
const pendingRequests = new Map();3. Implementing Request and Response Interceptors
You can intercept outgoing requests to check if an identical request is already pending. If it exists, return the stored promise instead of sending a new network request. When the request resolves or rejects, remove it from the map.
import axios from 'axios';
const apiClient = axios.create({
baseURL: 'https://api.example.com',
});
// Custom adapter to return cached in-flight promises
const defaultAdapter = apiClient.defaults.adapter;
apiClient.interceptors.request.use((config) => {
const requestKey = generateRequestKey(config);
if (pendingRequests.has(requestKey)) {
// Attach the existing promise to the config to bypass the network call
config.adapter = () => pendingRequests.get(requestKey);
} else {
// Create the network request promise using the default adapter
const requestPromise = defaultAdapter(config);
pendingRequests.set(requestKey, requestPromise);
}
return config;
});
apiClient.interceptors.response.use(
(response) => {
const requestKey = generateRequestKey(response.config);
pendingRequests.delete(requestKey);
return response;
},
(error) => {
if (error.config) {
const requestKey = generateRequestKey(error.config);
pendingRequests.delete(requestKey);
}
return Promise.reject(error);
}
);4. Alternative Wrapper Approach
Instead of overriding the adapter, you can wrap Axios calls in a helper function. This approach is often simpler to maintain in modular codebases:
export function deduplicatedRequest(config) {
const key = generateRequestKey(config);
if (pendingRequests.has(key)) {
return pendingRequests.get(key);
}
const promise = apiClient(config).finally(() => {
pendingRequests.delete(key);
});
pendingRequests.set(key, promise);
return promise;
}5. Handling Edge Cases
- Mutations: Only apply deduplication to idempotent
requests (primarily
GETrequests) unless specific business logic demands deduplicatingPOST,PUT, orDELETEcalls. - Cache Eviction: Always use a
finallyblock or handle both success and error branches in interceptors to ensure pending keys are cleared, preventing stale promises from blocking future requests.