Memoize async calls with a TTL cache

JavaScript
// Cache async results for a TTL; concurrent callers share the same promise.
function memoizeAsync(fn, ttlMs) {
  const cache = new Map();

  return async function memoized(...args) {
    const key = JSON.stringify(args);
    const now = Date.now();
    const hit = cache.get(key);
    if (hit && hit.expires > now) return hit.value;

    const value = Promise.resolve().then(() => fn.apply(this, args));
    const entry = { value, expires: now + ttlMs };
    cache.set(key, entry);
    value.catch(() => {
      if (cache.get(key) === entry) cache.delete(key);
    });
    return value;
  };
}

let calls = 0;
const getUser = memoizeAsync(async (id) => {
  calls += 1;
  return { id, loadedAt: Date.now() };
}, 60_000);

Promise.all([getUser(7), getUser(7)]).then(() => {
  console.log('loads:', calls); // 1
});