RequestDeduplicator - sebamar88/bytekit GitHub Wiki

🔗 RequestDeduplicator

EN: Deduplicates in-flight requests so multiple consumers calling the same endpoint at the same time share a single promise instead of firing duplicate network requests.

ES: Deduplica solicitudes en vuelo para que múltiples consumidores que llaman al mismo endpoint al mismo tiempo compartan una sola promesa en vez de disparar solicitudes de red duplicadas.


📦 Import

import { RequestDeduplicator } from "bytekit/request-deduplicator";
// or | o también:
import { RequestDeduplicator } from "bytekit";

⚙️ Constructor

const dedup = new RequestDeduplicator(config?: DeduplicatorConfig);

DeduplicatorConfig

Property / Propiedad Type / Tipo Default Description (EN) Descripción (ES)
keyGenerator (url: string, options?: Record<string, unknown>) => string (url + sorted options) Custom function to derive a dedup key Función personalizada para derivar la clave de deduplicación

📋 Methods / Métodos

Method / Método Signature / Firma Description (EN) Descripción (ES)
execute execute<T>(url: string, fn: () => Promise<T>, options?: Record<string, unknown>): Promise<T> Executes fn or returns an existing in-flight promise for the same key Ejecuta fn o devuelve una promesa en vuelo existente para la misma clave
getStats getStats(): DeduplicatorStats Returns deduplication statistics Devuelve estadísticas de deduplicación
getInFlightCount getInFlightCount(): number Returns the number of currently in-flight requests Devuelve el número de solicitudes actualmente en vuelo
clear clear(): void Clears all in-flight requests and resets stats Limpia todas las solicitudes en vuelo y reinicia estadísticas
resetStats resetStats(): void Resets counters without clearing in-flight requests Reinicia contadores sin limpiar solicitudes en vuelo

DeduplicatorStats

interface DeduplicatorStats {
  deduplicated: number;      // Requests that shared an existing promise / Solicitudes que compartieron una promesa existente
  total: number;             // Total execute() calls / Llamadas totales a execute()
  deduplicationRate: number; // deduplicated / total (0–1) / Tasa de deduplicación (0–1)
}

🧠 How It Works / Cómo Funciona

EN: When execute() is called, RequestDeduplicator generates a key from the URL (and optional options). If a request with the same key is already in-flight, the same promise is returned to all callers. Once the original promise resolves or rejects, the entry is removed and future calls create a new request.

ES: Cuando se llama a execute(), RequestDeduplicator genera una clave a partir de la URL (y opciones opcionales). Si una solicitud con la misma clave ya está en vuelo, la misma promesa se devuelve a todos los llamadores. Una vez que la promesa original se resuelve o rechaza, la entrada se elimina y futuras llamadas crean una nueva solicitud.

Caller A ──┐
            ├──▶ execute("/users") ──▶ fetch("/users") ──▶ Response
Caller B ──┘         ↑                                        │
                     │       same promise shared               │
                     └─────────────────────────────────────────┘

(Only ONE network request is made / Solo se hace UNA solicitud de red)

💡 Examples / Ejemplos

Basic Usage / Uso Básico

import { RequestDeduplicator } from "bytekit/request-deduplicator";

const dedup = new RequestDeduplicator();

// EN: Even if called 3 times simultaneously, only 1 fetch is made
// ES: Aunque se llame 3 veces simultáneamente, solo se hace 1 fetch
const [r1, r2, r3] = await Promise.all([
  dedup.execute("/api/users", () => fetch("/api/users").then(r => r.json())),
  dedup.execute("/api/users", () => fetch("/api/users").then(r => r.json())),
  dedup.execute("/api/users", () => fetch("/api/users").then(r => r.json())),
]);

// r1 === r2 === r3 (same reference / misma referencia)

In a React Hook / En un Hook de React

const dedup = new RequestDeduplicator();

function useUser(id: string) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // EN: If multiple components render with the same id,
    //     only one request is made
    // ES: Si múltiples componentes renderizan con el mismo id,
    //     solo se hace una solicitud
    dedup
      .execute(`/api/users/${id}`, () =>
        fetch(`/api/users/${id}`).then(r => r.json())
      )
      .then(setUser);
  }, [id]);

  return user;
}

With Options / Con Opciones

// EN: Different options = different dedup keys
// ES: Diferentes opciones = diferentes claves de deduplicación
await dedup.execute(
  "/api/users",
  () => fetch("/api/users?role=admin").then(r => r.json()),
  { role: "admin" }
);

await dedup.execute(
  "/api/users",
  () => fetch("/api/users?role=guest").then(r => r.json()),
  { role: "guest" }
);
// These are separate requests / Estas son solicitudes separadas

Monitoring Stats / Monitorear Estadísticas

const stats = dedup.getStats();

console.log(`Total calls: ${stats.total}`);
console.log(`Deduplicated: ${stats.deduplicated}`);
console.log(`Dedup rate: ${(stats.deduplicationRate * 100).toFixed(1)}%`);
console.log(`In-flight now: ${dedup.getInFlightCount()}`);

Error Handling / Manejo de Errores

// EN: If the original request fails, ALL consumers receive the same error
// ES: Si la solicitud original falla, TODOS los consumidores reciben el mismo error
try {
  await dedup.execute("/api/data", async () => {
    throw new Error("Network failure");
  });
} catch (error) {
  console.error(error.message); // "Network failure"
}

// EN: After failure, the in-flight entry is removed,
//     so the next call creates a fresh request
// ES: Después del fallo, la entrada en vuelo se elimina,
//     por lo que la siguiente llamada crea una solicitud nueva

Custom Key Generator / Generador de Claves Personalizado

const dedup = new RequestDeduplicator({
  keyGenerator: (url, options) => {
    // EN: Deduplicate based on URL path only (ignore query string)
    // ES: Deduplicar basándose solo en la ruta de URL (ignorar query string)
    return new URL(url, "http://localhost").pathname;
  },
});

⚠️ Important Notes / Notas Importantes

EN: Deduplication only applies to in-flight requests. Once a request completes, the next call to execute() with the same key will start a new request. If you need caching of completed responses, combine with RequestCache.

ES: La deduplicación solo aplica a solicitudes en vuelo. Una vez que una solicitud se completa, la siguiente llamada a execute() con la misma clave iniciará una nueva solicitud. Si necesitas cachear respuestas completadas, combina con RequestCache.


🔗 See Also / Véase También

  • RequestCache — Cache completed responses with TTL / Cachear respuestas completadas con TTL
  • RateLimiter — Limit request frequency per URL / Limitar frecuencia de solicitudes por URL
  • ApiClient — HTTP client that can integrate deduplication / Cliente HTTP que puede integrar deduplicación