type Loader<T> = () => Promise<T>;
interface Entry<T> {
value: T;
expiresAt: number;
}
export class AsyncTtlCache<T> {
private fresh = new Map<string, Entry<T>>();
private inflight = new Map<string, Promise<T>>();
constructor(private readonly ttlMs: number) {}
get(key: string, loader: Loader<T>): Promise<T> {
const cached = this.fresh.get(key);
if (cached && cached.expiresAt > Date.now()) {
return Promise.resolve(cached.value);
}
const pending = this.inflight.get(key);
if (pending) {
return pending;
}
const request = loader()
.then((value) => {
this.fresh.set(key, { value, expiresAt: Date.now() + this.ttlMs });
this.inflight.delete(key);
return value;
})
.catch((err) => {
// Do not cache failures; let the next caller retry.
this.inflight.delete(key);
throw err;
});
this.inflight.set(key, request);
return request;
}
invalidate(key: string): void {
this.fresh.delete(key);
this.inflight.delete(key);
}
clear(): void {
this.fresh.clear();
this.inflight.clear();
}
stats(): { fresh: number; inflight: number } {
return { fresh: this.fresh.size, inflight: this.inflight.size };
}
}
import { AsyncTtlCache } from "./AsyncTtlCache";
interface User {
id: string;
name: string;
email: string;
}
// One shared instance so every caller dedupes and shares the TTL window.
export const userCache = new AsyncTtlCache<User>(30_000);
export async function fetchUser(id: string): Promise<User> {
return userCache.get(`user:${id}`, async () => {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) {
throw new Error(`Failed to load user ${id}: ${res.status}`);
}
return (await res.json()) as User;
});
}
export function invalidateUser(id: string): void {
userCache.invalidate(`user:${id}`);
}
import { useEffect, useState } from "react";
interface ResourceState<T> {
data: T | null;
error: Error | null;
loading: boolean;
}
export function useCachedResource<T>(
key: string,
loader: () => Promise<T>,
): ResourceState<T> {
const [state, setState] = useState<ResourceState<T>>({
data: null,
error: null,
loading: true,
});
useEffect(() => {
let cancelled = false;
setState((prev) => ({ ...prev, loading: true }));
loader()
.then((data) => {
if (!cancelled) {
setState({ data, error: null, loading: false });
}
})
.catch((error: Error) => {
if (!cancelled) {
setState({ data: null, error, loading: false });
}
});
return () => {
cancelled = true;
};
}, [key]);
return state;
}
import { useCachedResource } from "./useCachedResource";
import { fetchUser } from "./userCache";
interface Props {
userId: string;
}
export function UserBadge({ userId }: Props) {
const { data, error, loading } = useCachedResource(
`user:${userId}`,
() => fetchUser(userId),
);
if (loading) return <span className="badge badge--loading">Loading…</span>;
if (error) return <span className="badge badge--error">{error.message}</span>;
if (!data) return null;
return (
<span className="badge" title={data.email}>
{data.name}
</span>
);
}
This snippet builds a small async cache that solves two related problems at once: repeated expensive calls returning the same data (solved with a time-to-live cache) and the thundering-herd problem where many callers request the same key simultaneously before any result is cached (solved with in-flight deduplication).
The AsyncTtlCache tab stores two maps: fresh holds resolved values with an expiresAt timestamp, and inflight holds the pending Promise for keys currently being fetched. The core method get first checks fresh; if an entry exists and has not expired it is returned immediately. If a fetch for the same key is already running, get returns that same shared Promise instead of starting a second one — this is the deduplication step, and it is what prevents ten concurrent components from firing ten identical network requests.
When neither a fresh value nor an in-flight promise exists, get calls the supplied loader, stores the resulting promise in inflight, and attaches handlers. On success the value is written to fresh with expiresAt = now + ttlMs and the in-flight entry is cleared. Crucially, on failure the in-flight entry is also cleared inside a finally-style path so a rejected fetch does not poison the key forever; the next caller retries cleanly. Errors are intentionally not cached, since caching a transient failure is usually worse than retrying.
The invalidate and clear methods let callers evict stale data after a mutation, and stats exposes counts for debugging. A subtle detail is that expiresAt is checked lazily on read rather than with timers, avoiding a background sweep and keeping the structure simple; expired entries are simply overwritten on next access.
The useCachedResource hook tab shows real usage in React. It reads through a shared cache instance so multiple mounted components hitting the same key share one request, tracks data, error, and loading state, and guards against setting state after unmount with a cancelled flag. This pattern is ideal for autocomplete, dashboards, and any UI where the same expensive query is requested from many places at once.
Related snips
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
use crossbeam::channel::unbounded;
use std::thread;
fn main() {
let (tx, rx) = unbounded();
Crossbeam for advanced concurrent data structures
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
const success = true;
setTimeout(() => {
if (success) {
Promises and async/await patterns for asynchronous JavaScript
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
Channels (mpsc) for message passing between threads
export type Settled<R> =
| { status: 'fulfilled'; value: R }
| { status: 'rejected'; reason: unknown };
export interface ConcurrencyOptions {
limit: number;
Simple concurrency limiter for batch operations
Share this code
Here's the card — post it anywhere.