JAVASCRIPT
JavaScript: Simple API Request with Caching and Stale-While-Revalidate
Implement client-side caching for API requests with a 'stale-while-revalidate' strategy, improving perceived performance and reducing unnecessary network calls.
const cache = new Map();
async function fetchDataWithCaching(url, options = {}) {
const cacheKey = JSON.stringify({ url, options });
const cacheEntry = cache.get(cacheKey);
const now = Date.now();
// Define cache validity periods (in milliseconds)
const STALE_AFTER = 5 * 60 * 1000; // 5 minutes
const EXPIRE_AFTER = 60 * 60 * 1000; // 60 minutes
if (cacheEntry) {
const { data, timestamp } = cacheEntry;
// Serve stale data immediately if within EXPIRE_AFTER
if (now - timestamp < EXPIRE_AFTER) {
console.log(`[Cache] Serving ${url} from cache.`);
// If data is stale, revalidate in background
if (now - timestamp > STALE_AFTER && !cacheEntry.revalidating) {
console.log(`[Cache] ${url} is stale, revalidating in background.`);
cacheEntry.revalidating = true;
fetch(url, options)
.then(response => response.json())
.then(newData => {
cache.set(cacheKey, { data: newData, timestamp: Date.now(), revalidating: false });
console.log(`[Cache] ${url} revalidated successfully.`);
})
.catch(error => console.error(`[Cache] Background revalidation failed for ${url}:`, error))
.finally(() => cacheEntry.revalidating = false);
}
return data;
} else {
// Cache expired, remove it
cache.delete(cacheKey);
console.log(`[Cache] ${url} cache expired.`);
}
}
// No cache entry or expired, fetch new data
console.log(`[Cache] Fetching fresh data for ${url}.`);
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const freshData = await response.json();
cache.set(cacheKey, { data: freshData, timestamp: Date.now(), revalidating: false });
return freshData;
} catch (error) {
console.error(`Error fetching data for ${url}:`, error);
throw error;
}
}
// Example Usage:
async function demoCaching() {
const apiEndpoint = 'https://jsonplaceholder.typicode.com/posts/1';
console.log('
--- First Request (should fetch fresh) ---');
let data1 = await fetchDataWithCaching(apiEndpoint);
console.log('Data 1:', data1.title);
console.log('
--- Second Request (should be from cache) ---');
let data2 = await fetchDataWithCaching(apiEndpoint);
console.log('Data 2:', data2.title);
// Simulate passing the stale-after time
console.log('
--- Simulating time passing (5.5 minutes for stale) ---');
// For demonstration only: Manually adjust timestamp to simulate staleness
const cacheKey = JSON.stringify({ url: apiEndpoint, options: {} });
const entry = cache.get(cacheKey);
if (entry) {
entry.timestamp = Date.now() - (5.5 * 60 * 1000); // Make it stale
}
console.log('
--- Third Request (should return stale, revalidate in background) ---');
let data3 = await fetchDataWithCaching(apiEndpoint);
console.log('Data 3 (stale):', data3.title);
console.log('
--- Wait for background revalidation... (2 seconds) ---');
await new Promise(resolve => setTimeout(resolve, 2000));
console.log('
--- Fourth Request (should be fresh from background revalidation) ---');
let data4 = await fetchDataWithCaching(apiEndpoint);
console.log('Data 4 (fresh):', data4.title);
}
demoCaching();
How it works: This JavaScript snippet implements a client-side caching strategy for API requests, specifically using the "stale-while-revalidate" pattern. When data is requested, it first checks a local `Map` cache. If a valid, non-expired entry exists, it's returned immediately to improve perceived performance. If the cached data is 'stale' (older than `STALE_AFTER` duration) but not yet 'expired' (older than `EXPIRE_AFTER`), the stale data is returned, and a fresh data fetch is initiated in the background to update the cache. If no valid cache entry exists, new data is fetched from the API and then stored in the cache. This approach balances responsiveness with data freshness, ensuring users see content quickly while data updates asynchronously.