|
|
|
|
|
by nurple
823 days ago
|
|
Don't forget to fung futures that are fungible for the same key. ETA: I appreciate the time you took to make the example, also I changed the extension to `mjs` so the async IIFE isn't needed. const CACHE_EXPIRY = 1000; // Cache expiry time in milliseconds
let cache = {}; // Shared cache object
let futurecache = {}; // Shared cache of future values
function getFromCache(key) {
const cachedData = cache[key];
if (cachedData && Date.now() - cachedData.timestamp < CACHE_EXPIRY) {
return cachedData.data;
}
return null; // Cache entry expired or not found
}
function updateCache(key, data) {
cache[key] = {
data,
timestamp: Date.now(),
};
}
var mockFetchCount = 0;
// simulate web request shorter than cache time
async function mockFetch(url) {
await new Promise(resolve => setTimeout(resolve, 100));
mockFetchCount += 1;
return `result from ${url}`;
}
async function fetchDataAndUpdateCache(key) {
// maybe its value is cached already
const cachedData = getFromCache(key);
if (cachedData) {
return cachedData;
}
// maybe its value is already being fetched
const future = futurecache[key];
if(future) {
return future;
}
// Simulate fetching data from an external source
const futureData = mockFetch(`https://example.com/data/${key}`); // Placeholder fetch
futurecache[key] = futureData;
const newData = await futureData;
delete futurecache[key];
updateCache(key, newData);
return newData;
}
const key = 'myData';
// Fetch data twice in a sequence - OK
await fetchDataAndUpdateCache(key);
await fetchDataAndUpdateCache(key);
console.log('mockFetchCount should be 1:', mockFetchCount);
// Reset counter and wait cache expiry
mockFetchCount = 0;
await new Promise(resolve => setTimeout(resolve, CACHE_EXPIRY));
// Fetch data twice concurrently - we executed fetch twice!
await Promise.all([...Array(100)].map(() => fetchDataAndUpdateCache(key)));
console.log('mockFetchCount should be 1:', mockFetchCount);
|
|
Note that, as wolfgang42 has shown in a sibling comment, the original cache map isn't necessary if you're using a future map, since the futures already contain the result:
---But note that this kind of problem is much easier to fix than to actually diagnose.
My hypothesis is that the lax attutide of Node programmers towards concurrency is what causes subtle bugs like these to happen in the first place.
Python, for example, also has single-threaded async concurrency like Node, but unlike Node it also has all the standard synchronization primitives also implemented in asyncio: https://docs.python.org/3/library/asyncio-sync.html