I am writing a service in Nodejs in which I fetch prices from an Api and the call to the API might take over a minute so one of the things that can happen is that a request for a specific item can happen and the same item can be requested before the first item is returned and I want to detect if there is an in-flight request for a specified item and, if there is one, I need to wait for this request to be finished and return the same response for both requests.
An example diagram would be:
00.000 getCost('123') #1 call
00.001 getExternalCost('123') query
01.000 getCost('123') #2 call
90.001 getExternalCost('123') response
90.002 getCost('123') #1 response
90.003 getCost('123') #2 response
This is the code I have written so far, which simply fetches the cost of the item.
let cache = new Map();
let addToCache = (key,val) => {
if(!cache.has(key)){
cache.set(key,val);
}
}
const getCost = async (itemId) => {
if(cache.has(itemId)){
return cache.get(itemId);
}
const price = await getExternalCost(itemId);
addToCache(itemId,price);
return cost;
}
CodePudding user response:
This can be done if you add promises to the cache rather than awaiting them. By doing this an incoming query can be redirected to receive the value of a similar query pending in the cache.
const cache = new Map();
const addToCache = (key, val) => {
if (!cache.has(key)) {
cache.set(key, val);
}
};
const retrieveFromCache = async(itemId) => {
try{
const start = Date.now();
const price = await cache.get(itemId);
const end = Date.now();
console.log(`Query for ${itemId}. Price: ${price}. Time spent: ${(end - start)/1000} seconds.`);
return price;
} catch (err) {
/*
If required insert logic here to remove the itemId from the cache to
allow new attempts to getExternalCost on this itemId.
*/
return err;
}
};
const getCost = async(itemId) => {
if (cache.has(itemId)) {
const start = Date.now();
const price = await retrieveFromCache(itemId);
return price;
}
const pricePromise = getExternalCost(itemId);
addToCache(itemId, pricePromise);
// all queries go through the cache
const price = await retrieveFromCache(itemId);
return price;
};
// example code
function getExternalCost(itemId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(itemId * 100);
}, 1000);
});
}
let counter = 0;
const itemIds = [10, 10, 12, 12, 10, 1];
// mimic incoming queries
let interval = setInterval(() => {
getCost(itemIds[counter ]);
if (counter === itemIds.length) {
clearInterval(interval);
}
}, 500);
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>