Cached Fetch Utility

Prompt
Create a JavaScript function to implement a caching mechanism for API calls.
Requirements
- Support configurable cache expiry time
- Ability to enable/disable caching per request
- Provide methods to:
- Clear entire cache
- Clear cache for a specific URL
Example Usage
// Basic fetch with default caching
const breedsList = await cachedFetch(
'https://dog.ceo/api/breeds/list/all'
);
// Custom cache configuration
const breedsListCacheConfig = await cachedFetch(
'https://dog.ceo/api/breeds/list/all',
{ headers: { Authorization: 'Bearer token' } },
{
enableCache: true,
expiryTime: 10 * 60 * 1000, // 10 minutes
}
);
// Disable caching
const breedsListNoCache = await cachedFetch(
'https://dog.ceo/api/breeds/list/all',
{},
{ enableCache: false }
);
// Additional utility methods
cachedFetch.clearCache(); // Clear entire cache
cachedFetch.clearCache(
'https://dog.ceo/api/breeds/list/all'
); // Clear specific URL cache
Playground
Solution
Explanation
Let's break down this caching utility step by step! It's actually a really cool way to optimize API calls by storing responses we've already received.
First, we create a closure using createCachedFetch()
that gives us a private cache using Map
.
function createCachedFetch() {
const cache = new Map();
// ... rest of the code
}
Using a Map is perfect here because it gives us fast lookups by URL and makes it easy to store both the data and timestamp together.
The main function fetchWithCache
takes three parameters:
url
: Where we're fetching fromoptions
: Regular fetch options (like headers)cacheOptions
: Our special caching configuration
async function fetchWithCache(
url,
options = {},
cacheOptions = {}
) {
const enableCache = cacheOptions.enableCache ?? true;
const expiryTime = cacheOptions.expiryTime || DEFAULT_EXPIRY_TIME;
// ...
}
Here's where it gets interesting! Before we do anything, we check if caching is enabled (it is by default). If it's disabled, we just do a regular fetch:
if (!enableCache) {
const response = await fetch(url, options);
return response.json();
}
Now for the fun part - checking our cache! We look at the current time and compare it with when we last cached the data:
const currentTime = Date.now();
const cachedEntry = cache.get(url);
if (cachedEntry && currentTime - cachedEntry.timestamp < expiryTime) {
return cachedEntry.data;
}
If we have valid cached data that hasn't expired, why make another API call? We just return what we have!
If we don't have cached data (or it's expired), we fetch fresh data:
try {
const response = await fetch(url, options);
const data = await response.json();
cache.set(url, {
data,
timestamp: currentTime,
});
return data;
} catch (error) {
// Our fallback strategy - use stale cache if available
if (cachedEntry) {
console.warn('Fetch failed, returning stale cache', error);
return cachedEntry.data;
}
throw error;
}
Here's a neat trick - even if the data is technically expired, we keep it around as a fallback. If our fetch fails and we have old data, we'll return that instead of throwing an error. It's like having a backup plan!
Finally, we add some utility methods to manage the cache:
fetchWithCache.clearCache = (url) => {
if (url) {
cache.delete(url);
} else {
cache.clear();
}
};
This lets us clear either a specific URL's cache or wipe the entire cache clean.
Common Pitfalls
Common Pitfalls to Watch Out For:
-
Memory Management: The cache can grow quite large if you're not careful. Consider implementing a maximum cache size or periodically cleaning old entries.
-
Cache Invalidation: One of the two hard problems in computer science! Make sure your expiry times make sense for your data - some data might need to be fresh more often than others.
-
Error Handling: While using stale cache as fallback is helpful, make sure your application can handle cases where there's no cache and the fetch fails.
This caching utility is super useful for
- Reducing unnecessary API calls
- Improving application performance
- Providing offline fallback capabilities
- Managing API rate limits
Remember, the best caching strategy depends on your specific needs. You might want to adjust the default expiry time or add more sophisticated cache invalidation logic for your use case!