5月29日 22:54
What performance optimization techniques are there when using axios? How to reduce unnecessary network requests?
When using axios for HTTP requests, performance can be optimized in various ways to reduce unnecessary network overhead and improve user experience.
1. Request Caching
Memory Cache
javascriptclass AxiosCache { constructor() { this.cache = new Map(); this.ttl = 5 * 60 * 1000; // 5 minute cache } generateKey(config) { return `${config.method}-${config.url}-${JSON.stringify(config.params)}`; } get(config) { const key = this.generateKey(config); const cached = this.cache.get(key); if (cached && Date.now() - cached.timestamp < this.ttl) { return cached.data; } this.cache.delete(key); return null; } set(config, data) { const key = this.generateKey(config); this.cache.set(key, { data, timestamp: Date.now() }); } clear() { this.cache.clear(); } } const cache = new AxiosCache(); // Axios instance with cache const cachedApi = axios.create(); cachedApi.interceptors.request.use(config => { // Check cache const cached = cache.get(config); if (cached) { // Return cached data, cancel request config.adapter = () => Promise.resolve({ data: cached, status: 200, statusText: 'OK', headers: {}, config }); } return config; }); cachedApi.interceptors.response.use(response => { // Cache response data if (response.config.method === 'get') { cache.set(response.config, response.data); } return response; });
Using Cache API (Service Worker)
javascript// Cache requests in Service Worker self.addEventListener('fetch', event => { if (event.request.url.includes('/api/')) { event.respondWith( caches.match(event.request).then(response => { if (response) { return response; } return fetch(event.request).then(response => { const clone = response.clone(); caches.open('api-cache').then(cache => { cache.put(event.request, clone); }); return response; }); }) ); } });
2. Request Deduplication (Debounce)
javascriptclass RequestDeduper { constructor() { this.pendingRequests = new Map(); } generateKey(config) { return `${config.method}-${config.url}-${JSON.stringify(config.params)}-${JSON.stringify(config.data)}`; } async request(config) { const key = this.generateKey(config); // If there's an ongoing identical request, return that Promise if (this.pendingRequests.has(key)) { return this.pendingRequests.get(key); } // Create new request const promise = axios(config).finally(() => { this.pendingRequests.delete(key); }); this.pendingRequests.set(key, promise); return promise; } } const deduper = new RequestDeduper(); // Usage const fetchUser = (id) => deduper.request({ method: 'GET', url: `/api/users/${id}` }); // Call multiple times simultaneously, only one request will be sent fetchUser(1); fetchUser(1); fetchUser(1); // Three calls, one request
3. Request Batching
javascriptclass RequestBatcher { constructor() { this.batch = []; this.timeout = null; this.delay = 50; // Merge requests within 50ms } addRequest(request) { return new Promise((resolve, reject) => { this.batch.push({ request, resolve, reject }); clearTimeout(this.timeout); this.timeout = setTimeout(() => this.flush(), this.delay); }); } async flush() { if (this.batch.length === 0) return; const currentBatch = this.batch; this.batch = []; // Merge requests const ids = currentBatch.map(item => item.request.id); try { const response = await axios.post('/api/batch', { ids }); // Distribute results currentBatch.forEach((item, index) => { item.resolve(response.data[index]); }); } catch (error) { currentBatch.forEach(item => { item.reject(error); }); } } }
4. Lazy Loading and Pagination
javascript// Virtual scroll + pagination loading class VirtualListLoader { constructor(api, pageSize = 20) { this.api = api; this.pageSize = pageSize; this.cache = new Map(); this.loadingPages = new Set(); } async loadPage(page) { // Check cache if (this.cache.has(page)) { return this.cache.get(page); } // Prevent duplicate loading if (this.loadingPages.has(page)) { return new Promise(resolve => { const check = setInterval(() => { if (this.cache.has(page)) { clearInterval(check); resolve(this.cache.get(page)); } }, 100); }); } this.loadingPages.add(page); try { const response = await this.api.get('/api/items', { params: { page, pageSize: this.pageSize } }); this.cache.set(page, response.data); return response.data; } finally { this.loadingPages.delete(page); } } }
5. Request Priority Management
javascriptclass PriorityRequestQueue { constructor() { this.queue = []; this.maxConcurrent = 6; // Browser max concurrent requests this.running = 0; } add(config, priority = 0) { return new Promise((resolve, reject) => { this.queue.push({ config, priority, resolve, reject }); this.queue.sort((a, b) => b.priority - a.priority); this.process(); }); } async process() { if (this.running >= this.maxConcurrent || this.queue.length === 0) { return; } this.running++; const { config, resolve, reject } = this.queue.shift(); try { const response = await axios(config); resolve(response); } catch (error) { reject(error); } finally { this.running--; this.process(); } } } // Usage const queue = new PriorityRequestQueue(); // High priority request queue.add({ url: '/api/critical-data' }, 10); // Low priority request queue.add({ url: '/api/background-data' }, 1);
6. Compression and Request Minimization
javascript// Request data compression const compressRequest = (data) => { // Remove undefined and null values const cleaned = JSON.parse(JSON.stringify(data)); return cleaned; }; // Field minimization const minimizeFields = (fields) => { // Only request needed fields return fields.join(','); }; axios.get('/api/users', { params: { fields: minimizeFields(['id', 'name', 'avatar']), include: minimizeFields(['posts', 'comments']) } });
7. Using HTTP/2 Server Push
javascript// Server configuration for HTTP/2 Push // Add Link header in response // Link: </api/related-data>; rel=preload; as=fetch // Client preloading const preloadResources = () => { const links = document.querySelectorAll('link[rel=preload][as=fetch]'); links.forEach(link => { axios.get(link.href, { headers: { 'Purpose': 'prefetch' } }); }); };
8. Connection Reuse and Keep-Alive
javascript// Use same axios instance to reuse connections const api = axios.create({ baseURL: 'https://api.example.com', // Enable keep-alive (in Node.js) httpAgent: new http.Agent({ keepAlive: true }), httpsAgent: new https.Agent({ keepAlive: true }) }); // Browser automatically reuses connections
9. Request Timeout Optimization
javascript// Dynamically adjust timeout based on network conditions const getTimeout = () => { const connection = navigator.connection; if (connection) { switch (connection.effectiveType) { case '4g': return 10000; case '3g': return 20000; case '2g': return 30000; default: return 15000; } } return 10000; }; axios.get('/api/data', { timeout: getTimeout() });
10. Error Retry Strategy
javascriptaxios.interceptors.response.use(null, async (error) => { const { config } = error; if (!config || !config.retry) { return Promise.reject(error); } config.retryCount = config.retryCount || 0; if (config.retryCount >= config.retry) { return Promise.reject(error); } config.retryCount += 1; // Exponential backoff const backoff = Math.pow(2, config.retryCount) * 1000; await new Promise(resolve => setTimeout(resolve, backoff)); return axios(config); }); // Usage axios.get('/api/data', { retry: 3, retryDelay: 1000 });
11. Offline-First Strategy
javascript// Use IndexedDB for caching const offlineFirstRequest = async (config) => { try { // Try network request first const response = await axios(config); // Cache to IndexedDB await saveToIndexedDB(config, response.data); return response; } catch (error) { // Network failed, try to read from cache const cached = await getFromIndexedDB(config); if (cached) { return { data: cached, fromCache: true }; } throw error; } };
12. Monitoring and Analytics
javascript// Performance monitoring interceptor axios.interceptors.request.use(config => { config.metadata = { startTime: Date.now() }; return config; }); axios.interceptors.response.use(response => { const duration = Date.now() - response.config.metadata.startTime; // Report performance data analytics.track('api_request', { url: response.config.url, method: response.config.method, duration, status: response.status, size: JSON.stringify(response.data).length }); // Slow request warning if (duration > 3000) { console.warn(`Slow request: ${response.config.url} took ${duration}ms`); } return response; });
Best Practices Summary
| Optimization Strategy | Applicable Scenario | Expected Effect |
|---|---|---|
| Request Caching | Infrequently changing data | Reduce 50-90% requests |
| Request Deduplication | Rapid consecutive triggers | Reduce duplicate requests |
| Request Batching | Batch operations | Reduce request count |
| Pagination Loading | Long lists | Reduce initial load time |
| Priority Queue | Critical/non-critical requests | Improve critical request response |
| Data Compression | Large data transfers | Reduce transfer volume |
| Connection Reuse | Frequent requests | Reduce connection overhead |
| Smart Timeout | Unstable networks | Improve user experience |
| Error Retry | Temporary failures | Improve success rate |
| Offline-First | Weak network environments | Improve availability |