#javascript #node.js #api #asynchronous #async-await
Вопрос:
Я пишу службу в Nodejs, в которой я получаю цены из Api, и вызов API может занять более минуты, поэтому одна из вещей, которые могут произойти, заключается в том, что может произойти запрос на определенный элемент, и один и тот же элемент может быть запрошен до возвращения первого элемента, и я хочу определить, есть ли запрос на указанный элемент в полете, и, если он есть, мне нужно дождаться завершения этого запроса и вернуть один и тот же ответ для обоих запросов.
Примером диаграммы может быть:
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
Это код, который я написал до сих пор, который просто определяет стоимость товара.
let cache = new Map(); let addToCache = (key,val) =gt; { if(!cache.has(key)){ cache.set(key,val); } } const getCost = async (itemId) =gt; { if(cache.has(itemId)){ return cache.get(itemId); } const price = await getExternalCost(itemId); addToCache(itemId,price); return cost; }
Ответ №1:
Это можно сделать, если вы добавляете обещания в кэш, а не ждете их. Таким образом, входящий запрос может быть перенаправлен для получения значения аналогичного запроса, ожидающего в кэше.
const MAX_AGE = 1000; const cache = new Map(); const cacheContainsKey = key =gt; { if (!cache.has(key)) { return false; } const { _ts } = cache.get(key); const age = Date.now() - _ts; if (age lt;= MAX_AGE) { return true; } console.log(`key ${key} age exceeds MAX_AGE: ${age}. Deleting key from cache.`); cache.delete(key); return false; }; const addToCache = (key, val) =gt; { cache.set(key, { _ts: Date.now(), val }); }; const retrieveFromCache = async(itemId) =gt; { try { const start = Date.now(); const price = await cache.get(itemId).val; 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) =gt; { if (cacheContainsKey(itemId)) { 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) =gt; { setTimeout(() =gt; { resolve(itemId * 100); }, 1000); }); } let counter = 0; const itemIds = [10, 10, 12, 12, 10, 1]; // mimic incoming queries let interval = setInterval(() =gt; { getCost(itemIds[counter ]); if (counter === itemIds.length) { clearInterval(interval); } }, 500);
Комментарии:
1. Да, именно так я видел решение этой проблемы раньше. Кэшируйте обещание, и пусть любой вызывающий абонент, ищущий значение с обещанием, просто использует
promise.then()
илиawait promise
, чтобы получить значение.2. Что делать, если первый запрос такого рода возвращает обещание, которое отклоняется (из-за временной ошибки)? Все последующие попадания в кэш также вернут отклоненное обещание.
3. @jsejcksn Это хороший аргумент. Нужно будет что — то настроить для периодической очистки кэша. В качестве альтернативы, некоторая логика может быть добавлена специально для обработки этого в a
try/catch
для вызоваretrieveFromCache
. Однако необходимо будет проявить некоторую осторожность, так как это может легко просто ударить по конечной точке, которая выходит из строя и не даст других результатов, которые кэш может помочь защитить. Эта часть полностью зависит от контекста.4. @jsejcksn Скорректировал ответ, в котором кэш может быть обновлен на основе неудачного запроса.
5. @jsejcksn — Когда обещание отклоняется, вы удаляете его из кэша в тот момент, когда видите, что оно отклонено — сам код кэша также может отслеживать обещание и делать это. Если несколько запросов ожидали этого обещания, все они увидят отказ. Похоже, у тебя не может быть и того, и другого. Либо вы делитесь запросами во время полета, делясь обещаниями и результатами и ошибками, либо вы этого не делаете. Это кажется правильным способом сделать это.