Skip to content

Async Toolkit

Sebastian Martinez edited this page Apr 2, 2026 · 4 revisions

⚡ Async Toolkit

EN: A comprehensive, zero-dependency async utilities module for TypeScript. Provides Promise-based helpers for delays, timeouts, retries with backoff, parallel/sequential execution, racing, debouncing, and throttling. Works in Node.js 18+ and all modern browsers.

ES: Un módulo completo de utilidades asíncronas para TypeScript sin dependencias externas. Provee helpers basados en Promises para delays, timeouts, reintentos con backoff, ejecución paralela/secuencial, racing, debouncing y throttling. Funciona en Node.js 18+ y todos los navegadores modernos.


¿Por qué Async Toolkit? / Why Async Toolkit?

ES: El manejo de operaciones asíncronas es el núcleo de cualquier aplicación moderna. Errores de red, APIs lentas, operaciones concurrentes descontroladas, y la falta de cancelación llevan a bugs difíciles de detectar. bytekit/async resuelve todos estos problemas con una API tipada, composable y bien probada. Es el módulo más importante de la librería.

EN: Handling asynchronous operations is the core of any modern application. Network errors, slow APIs, uncontrolled concurrent operations, and lack of cancellation lead to hard-to-detect bugs. bytekit/async solves all of these problems with a typed, composable, and well-tested API. It is the most important module in the library.


📦 Import

import {
  // Error Classes
  TimeoutError,
  AbortError,
  RetryError,
  PoolTimeoutError,
  // Functions
  sleep,
  timeout,
  withTimeout,
  retry,
  parallel,
  sequential,
  race,
  allSettled,
  debounceAsync,
  throttleAsync,
  // Classes
  PromisePool,
} from "bytekit/async";

// Types
import type { PromisePoolOptions } from "bytekit/async";

// Or from the barrel | O desde el barrel:
import { sleep, retry, parallel, PromisePool } from "bytekit";

📖 Module Overview / Resumen del Módulo

Function / Función Purpose (EN) Propósito (ES)
sleep Promise-based delay with cancellation Delay basado en Promise con cancelación
timeout Add timeout to any promise Agrega timeout a cualquier promise
withTimeout Wrap a function with automatic timeout Envuelve una función con timeout automático
retry Retry with configurable backoff Reintento con backoff configurable
parallel Run tasks in parallel (with concurrency) Ejecuta tareas en paralelo (con concurrencia)
sequential Run tasks one by one (chained) Ejecuta tareas una por una (encadenadas)
race Enhanced Promise.race (AggregateError) Promise.race mejorado (AggregateError)
allSettled Categorized Promise.allSettled Promise.allSettled categorizado
debounceAsync Debounce async functions Debounce para funciones async
throttleAsync Throttle async functions Throttle para funciones async
PromisePool Reusable concurrency pool with timeout & error hooks Pool de concurrencia reutilizable con timeout y hooks de error

🚨 Error Classes / Clases de Error

EN: All error classes extend the native Error class and maintain proper stack traces via Error.captureStackTrace (V8 engines).

ES: Todas las clases de error extienden la clase nativa Error y mantienen stack traces correctos mediante Error.captureStackTrace (motores V8).

Error Class / Clase Constructor Properties / Propiedades Description (EN) Descripción (ES)
TimeoutError new TimeoutError(message, timeout) .timeout: number Thrown when a timeout elapses before the promise settles Lanzado cuando un timeout expira antes de que la promise se resuelva
AbortError new AbortError(message?) Thrown when an operation is cancelled via AbortSignal. Default message: "Operation aborted" Lanzado cuando una operación se cancela vía AbortSignal. Mensaje por defecto: "Operation aborted"
RetryError new RetryError(message, attempts, lastError) .attempts: number, .lastError: unknown Thrown when all retry attempts are exhausted Lanzado cuando todos los intentos de reintento se agotan
PoolTimeoutError new PoolTimeoutError(timeoutMs) Thrown by PromisePool when an individual task exceeds its per-task timeout Lanzado por PromisePool cuando una tarea individual supera su timeout por tarea

Usage Example / Ejemplo de Uso

import { TimeoutError, AbortError, RetryError } from "bytekit/async";

try {
  await someAsyncOperation();
} catch (error) {
  if (error instanceof TimeoutError) {
    console.error(`Timed out after ${error.timeout}ms`);
  } else if (error instanceof AbortError) {
    console.log("Operation was cancelled by the user");
  } else if (error instanceof RetryError) {
    console.error(`Failed after ${error.attempts} attempts. Last error:`, error.lastError);
  }
}

📐 Types / Tipos

BackoffStrategy

type BackoffStrategy =
  | "linear"        // delay = baseDelay × attempt
  | "exponential"   // delay = baseDelay × 2^(attempt - 1)
  | ((attempt: number) => number);  // Custom function

EN: Controls how the delay grows between retry attempts. You can provide a custom function for full control.

ES: Controla cómo crece el delay entre intentos de reintento. Puedes proveer una función custom para control total.

Task & SequentialTask

type Task<T> = () => Promise<T>;
type SequentialTask<T> = (previousResult?: any) => Promise<T>;

AsyncFunction

type AsyncFunction<TArgs extends any[], TReturn> = (...args: TArgs) => Promise<TReturn>;

RetryOptions

Property / Propiedad Type / Tipo Default Description (EN) Descripción (ES)
maxAttempts number 3 Maximum number of attempts Número máximo de intentos
baseDelay number 1000 Base delay in ms between retries Delay base en ms entre reintentos
maxDelay number Infinity Cap for the computed delay Límite máximo para el delay calculado
backoff BackoffStrategy "exponential" Strategy for increasing delay Estrategia para aumentar el delay
shouldRetry (error: unknown) => boolean undefined Predicate to filter retryable errors Predicado para filtrar errores reintentables
signal AbortSignal undefined Signal for cancellation support Signal para soporte de cancelación

ParallelOptions

Property / Propiedad Type / Tipo Default Description (EN) Descripción (ES)
concurrency number undefined (unlimited) Max concurrent task executions Máximo de ejecuciones concurrentes

SequentialOptions

Property / Propiedad Type / Tipo Default Description (EN) Descripción (ES)
continueOnError boolean false Whether to continue executing after a task fails Si continuar ejecutando después de que falle una tarea

AllSettledResult<T>

interface AllSettledResult<T> {
  fulfilled: Array<{ value: T; index: number }>;
  rejected:  Array<{ reason: unknown; index: number }>;
}

DebounceOptions

Property / Propiedad Type / Tipo Default Description (EN) Descripción (ES)
leading boolean false Execute on the leading edge (first call) Ejecutar en el borde de inicio (primera llamada)
trailing boolean true Execute on the trailing edge (after delay) Ejecutar en el borde final (después del delay)

DebouncedFunction<TArgs, TReturn>

interface DebouncedFunction<TArgs extends any[], TReturn> {
  (...args: TArgs): Promise<TReturn>;  // Call the debounced function
  cancel(): void;                       // Cancel any pending execution
  flush(): Promise<TReturn | undefined>; // Immediately execute pending call
}

ThrottleOptions

Property / Propiedad Type / Tipo Default Description (EN) Descripción (ES)
trailing boolean true Execute the last queued call after the interval Ejecutar la última llamada encolada después del intervalo

ThrottledFunction<TArgs, TReturn>

interface ThrottledFunction<TArgs extends any[], TReturn> {
  (...args: TArgs): Promise<TReturn>;  // Call the throttled function
  cancel(): void;                       // Cancel any queued execution
}

PromisePoolOptions

Property / Propiedad Type / Tipo Default Description (EN) Descripción (ES)
concurrency number Max tasks running simultaneously. Minimum: 1 Máximo de tareas ejecutándose simultáneamente. Mínimo: 1
timeout number undefined Per-task timeout in ms. Must be > 0 Timeout por tarea en ms. Debe ser > 0
onError (error: Error, taskIndex: number) => void undefined Callback invoked when a task fails. Pool continues after being called Callback invocado cuando una tarea falla. El pool continúa tras ser invocado

🛏️ sleep

EN: Creates a Promise-based delay with optional AbortSignal cancellation. Ideal for waiting, polling intervals, and animation timing.

ES: Crea un delay basado en Promise con cancelación opcional vía AbortSignal. Ideal para esperas, intervalos de polling y temporización de animaciones.

Signature / Firma

function sleep(ms: number, signal?: AbortSignal): Promise<void>
Parameter / Parámetro Type / Tipo Required Description (EN) Descripción (ES)
ms number Duration in milliseconds (must be ≥ 0) Duración en milisegundos (debe ser ≥ 0)
signal AbortSignal Signal for cancellation Signal para cancelación
Throws / Lanza When / Cuándo
TypeError ms is negative / ms es negativo
AbortError Signal is aborted / Signal está abortado

Examples / Ejemplos

import { sleep, AbortError } from "bytekit/async";

// EN: Basic delay — wait 2 seconds
// ES: Delay básico — espera 2 segundos
await sleep(2000);
console.log("2 seconds later!");

// EN: Cancellable delay
// ES: Delay cancelable
const controller = new AbortController();

// Cancel after 500ms
setTimeout(() => controller.abort(), 500);

try {
  await sleep(5000, controller.signal);
} catch (error) {
  if (error instanceof AbortError) {
    console.log("Sleep was cancelled early!"); // ✅ this runs
  }
}

// EN: Zero-ms delay (microtask yield)
// ES: Delay de cero ms (yield de microtask)
await sleep(0); // resolves immediately

⏱️ timeout

EN: Wraps an existing promise with a timeout. If the promise doesn't settle before the deadline, it rejects with TimeoutError. The original promise continues running (Promises are not cancellable).

ES: Envuelve una promise existente con un timeout. Si la promise no se resuelve antes del límite, se rechaza con TimeoutError. La promise original sigue ejecutándose (las Promises no son cancelables).

Signature / Firma

function timeout<T>(promise: Promise<T>, ms: number, message?: string): Promise<T>
Parameter / Parámetro Type / Tipo Required Description (EN) Descripción (ES)
promise Promise<T> The promise to add timeout to La promise a la que agregar timeout
ms number Timeout duration in ms (must be ≥ 0) Duración del timeout en ms (debe ser ≥ 0)
message string Custom error message Mensaje de error personalizado
Throws / Lanza When / Cuándo
TimeoutError Promise doesn't settle in time / La promise no se resuelve a tiempo
TypeError First argument is not a Promise, or ms is invalid / El primer argumento no es una Promise, o ms es inválido

Example / Ejemplo

import { timeout, TimeoutError } from "bytekit/async";

// EN: Fetch with a 5-second timeout
// ES: Fetch con timeout de 5 segundos
try {
  const data = await timeout(fetch("/api/data"), 5000);
  console.log("Got data:", data);
} catch (error) {
  if (error instanceof TimeoutError) {
    console.error(`Request timed out after ${error.timeout}ms`);
  }
}

// EN: With custom error message
// ES: Con mensaje de error personalizado
await timeout(
  longRunningQuery(),
  10_000,
  "Database query exceeded 10s limit"
);

⏱️ withTimeout

EN: Wraps an async function (not a promise) so that every invocation automatically has timeout behavior. Useful when you want to enforce a time limit on all calls to a function.

ES: Envuelve una función async (no una promise) para que cada invocación tenga comportamiento de timeout automáticamente. Útil cuando quieres imponer un límite de tiempo en todas las llamadas a una función.

Signature / Firma

function withTimeout<TArgs extends any[], TReturn>(
  fn: (...args: TArgs) => Promise<TReturn>,
  ms: number,
  message?: string
): (...args: TArgs) => Promise<TReturn>
Parameter / Parámetro Type / Tipo Required Description (EN) Descripción (ES)
fn (...args) => Promise The async function to wrap La función async a envolver
ms number Timeout in ms for each call Timeout en ms por cada llamada
message string Custom error message Mensaje de error personalizado

Example / Ejemplo

import { withTimeout } from "bytekit/async";

// EN: Create a fetch function that always times out after 3 seconds
// ES: Crear una función fetch que siempre tiene timeout tras 3 segundos
const fetchUser = async (id: number) => {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
};

const fetchUserWithTimeout = withTimeout(fetchUser, 3000);

// EN: Every call now has a 3s timeout — no need to wrap each time
// ES: Cada llamada ahora tiene un timeout de 3s — sin necesidad de envolver cada vez
const user = await fetchUserWithTimeout(42);

🔄 retry

EN: Retries a failed async operation with configurable backoff strategy, delay caps, selective retry predicates, and AbortSignal cancellation. This is the most powerful function in the module.

ES: Reintenta una operación async fallida con estrategia de backoff configurable, límites de delay, predicados de reintento selectivo y cancelación con AbortSignal. Es la función más poderosa del módulo.

Signature / Firma

function retry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>
Parameter / Parámetro Type / Tipo Required Description (EN) Descripción (ES)
fn () => Promise<T> The async function to retry La función async a reintentar
options RetryOptions Retry configuration (see RetryOptions) Configuración de reintento (ver RetryOptions)
Throws / Lanza When / Cuándo
RetryError All attempts exhausted / Todos los intentos agotados
AbortError Signal is aborted / Signal está abortado
Original error shouldRetry returns false / shouldRetry retorna false

Backoff Strategies Diagram / Diagrama de Estrategias de Backoff

Attempt:        1         2         3         4         5
                │         │         │         │         │
Linear:         ├─ 1000 ──├─ 2000 ──├─ 3000 ──├─ 4000 ──├─ 5000ms
(baseDelay × n) │         │         │         │         │
                │         │         │         │         │
Exponential:    ├─ 1000 ──├─ 2000 ──├─ 4000 ──├─ 8000 ──├─ 16000ms
(baseDelay×2^n) │         │         │         │         │
                │         │         │         │         │
Custom fn:      ├─ f(1) ──├─ f(2) ──├─ f(3) ──├─ f(4) ──├─ f(5)

Retry Flow Diagram / Diagrama de Flujo de Reintento

                    ┌──────────────┐
                    │  Call fn()   │
                    └──────┬───────┘
                           │
                     ┌─────▼──────┐
                     │  Success?  │──── Yes ──▶ Return result ✅
                     └─────┬──────┘
                           │ No
                     ┌─────▼──────────┐
                     │ shouldRetry?   │──── No ──▶ Throw original error 🚫
                     │ (if provided)  │
                     └─────┬──────────┘
                           │ Yes (or no predicate)
                     ┌─────▼──────────┐
                     │ Last attempt?  │──── Yes ──▶ Throw RetryError ❌
                     └─────┬──────────┘
                           │ No
                     ┌─────▼──────────┐
                     │ signal.aborted │──── Yes ──▶ Throw AbortError 🛑
                     │   ?            │
                     └─────┬──────────┘
                           │ No
                     ┌─────▼──────────┐
                     │ sleep(delay)   │
                     │ with backoff   │
                     └─────┬──────────┘
                           │
                           ▼
                    (loop back to fn())

Examples / Ejemplos

import { retry, RetryError, AbortError } from "bytekit/async";

// EN: Basic retry with defaults (3 attempts, exponential backoff, 1s base delay)
// ES: Reintento básico con valores por defecto (3 intentos, backoff exponencial, 1s delay base)
const data = await retry(() => fetch("/api/data").then(r => r.json()));

// EN: Custom configuration
// ES: Configuración personalizada
const result = await retry(() => unreliableApiCall(), {
  maxAttempts: 5,
  baseDelay: 500,
  maxDelay: 10_000,
  backoff: "linear",
  shouldRetry: (error) => {
    // EN: Only retry on network errors, not 4xx
    // ES: Solo reintentar en errores de red, no en 4xx
    return error instanceof TypeError; // fetch network error
  },
});

// EN: With AbortSignal for cancellation
// ES: Con AbortSignal para cancelación
const controller = new AbortController();
setTimeout(() => controller.abort(), 15_000); // Cancel after 15s

try {
  await retry(() => connectToDatabase(), {
    maxAttempts: 10,
    baseDelay: 2000,
    signal: controller.signal,
  });
} catch (error) {
  if (error instanceof AbortError) {
    console.log("Retry cancelled by user");
  } else if (error instanceof RetryError) {
    console.error(`All ${error.attempts} attempts failed. Last:`, error.lastError);
  }
}

// EN: Custom backoff function (e.g., Fibonacci-like)
// ES: Función de backoff personalizada (e.g., tipo Fibonacci)
await retry(() => callService(), {
  maxAttempts: 6,
  backoff: (attempt) => Math.min(1000 * Math.pow(1.618, attempt), 30_000),
});

🚀 parallel

EN: Executes multiple async tasks in parallel. Without concurrency limit, uses Promise.all for maximum performance. With a concurrency option, uses a queue-based worker pattern to limit simultaneous executions. Results are always in original order.

ES: Ejecuta múltiples tareas async en paralelo. Sin límite de concurrencia, usa Promise.all para máximo rendimiento. Con una opción de concurrencia, usa un patrón de workers basado en cola para limitar ejecuciones simultáneas. Los resultados siempre están en orden original.

Signature / Firma

function parallel<T>(tasks: Array<Task<T>>, options?: ParallelOptions): Promise<T[]>
Parameter / Parámetro Type / Tipo Required Description (EN) Descripción (ES)
tasks Array<Task<T>> Array of () => Promise<T> functions Array de funciones () => Promise<T>
options ParallelOptions Concurrency configuration Configuración de concurrencia
Throws / Lanza When / Cuándo
TypeError Invalid tasks array or concurrency value / Array de tareas inválido o valor de concurrencia inválido
First task error Any task throws (fail-fast) / Cualquier tarea lanza error (fail-fast)

Concurrency Diagram / Diagrama de Concurrencia

Without concurrency limit (Promise.all):
  Task 1: ████████████████░░░░░░░░░░░░░░░░
  Task 2: ██████████░░░░░░░░░░░░░░░░░░░░░░
  Task 3: ████████████████████████░░░░░░░░░
  Task 4: ██████░░░░░░░░░░░░░░░░░░░░░░░░░░
                                          ▲ All done

With concurrency: 2 (queue-based workers):
  Worker 1: [Task 1 ████████] [Task 3 ████████████]
  Worker 2: [Task 2 ██████] [Task 4 ████]
                                         ▲ All done

Examples / Ejemplos

import { parallel } from "bytekit/async";

// EN: Unlimited concurrency — all at once
// ES: Concurrencia ilimitada — todas a la vez
const users = await parallel([
  () => fetchUser(1),
  () => fetchUser(2),
  () => fetchUser(3),
  () => fetchUser(4),
]);
// users = [user1, user2, user3, user4] (in order)

// EN: Limited concurrency — max 2 at a time (good for rate-limited APIs)
// ES: Concurrencia limitada — máx 2 a la vez (bueno para APIs con rate limit)
const pages = await parallel(
  urls.map(url => () => fetch(url).then(r => r.json())),
  { concurrency: 2 }
);

// EN: Processing 100 images, 5 at a time
// ES: Procesando 100 imágenes, 5 a la vez
const results = await parallel(
  images.map(img => () => processImage(img)),
  { concurrency: 5 }
);

📋 sequential

EN: Executes async tasks one by one, in order. Each task receives the result of the previous task as an optional parameter, enabling pipeline patterns. With continueOnError: true, failed tasks store the error as the result and execution continues.

ES: Ejecuta tareas async una por una, en orden. Cada tarea recibe el resultado de la tarea anterior como parámetro opcional, habilitando patrones de pipeline. Con continueOnError: true, las tareas fallidas almacenan el error como resultado y la ejecución continúa.

Signature / Firma

function sequential<T>(tasks: Array<SequentialTask<T>>, options?: SequentialOptions): Promise<T[]>
Parameter / Parámetro Type / Tipo Required Description (EN) Descripción (ES)
tasks Array<SequentialTask<T>> Array of (prevResult?) => Promise<T> functions Array de funciones (prevResult?) => Promise<T>
options SequentialOptions Error handling options Opciones de manejo de errores
Throws / Lanza When / Cuándo
TypeError Invalid tasks array / Array de tareas inválido
First task error Default behavior when a task fails / Comportamiento por defecto cuando una tarea falla

Pipeline Diagram / Diagrama de Pipeline

Task 1           Task 2           Task 3
┌──────┐  result  ┌──────┐  result  ┌──────┐
│ fn() │────────▶│ fn(r) │────────▶│ fn(r) │──▶ [r1, r2, r3]
└──────┘          └──────┘          └──────┘

Examples / Ejemplos

import { sequential } from "bytekit/async";

// EN: Pipeline — each task uses the previous result
// ES: Pipeline — cada tarea usa el resultado anterior
const results = await sequential([
  ()     => fetchUser(1),            // → user
  (user) => fetchPosts(user.id),     // → posts
  (posts) => enrichPosts(posts),     // → enrichedPosts
]);
// results = [user, posts, enrichedPosts]

// EN: Continue on error — errors stored as results
// ES: Continuar en error — errores almacenados como resultados
const results = await sequential(
  [
    () => fetchFromPrimary(),
    () => fetchFromBackup(),
    () => fetchFromCache(),
  ],
  { continueOnError: true }
);
// results might include Error objects for failed tasks

🏁 race

EN: An enhanced Promise.race implementation. If any promise fulfills, resolves with the first value. If all promises reject, rejects with an AggregateError containing every rejection reason — unlike native Promise.race which only rejects with the first error.

ES: Una implementación mejorada de Promise.race. Si alguna promise se cumple, se resuelve con el primer valor. Si todas las promises se rechazan, rechaza con un AggregateError que contiene todas las razones de rechazo — a diferencia del Promise.race nativo que solo rechaza con el primer error.

Signature / Firma

function race<T>(promises: Array<Promise<T>>): Promise<T>
Parameter / Parámetro Type / Tipo Required Description (EN) Descripción (ES)
promises Array<Promise<T>> Promises to race Promises a competir
Throws / Lanza When / Cuándo
AggregateError ALL promises reject / TODAS las promises se rechazan
TypeError Empty array or non-array / Array vacío o no-array

Comparison: race vs Promise.race

Scenario: 2 of 3 promises reject, 1 resolves

  Promise.race: ✅ Resolves (same as race)
  race:         ✅ Resolves (same)

Scenario: ALL 3 promises reject

  Promise.race: ❌ Rejects with FIRST error only
  race:         ❌ Rejects with AggregateError([err1, err2, err3])
                   ▲ You can inspect ALL errors!

Example / Ejemplo

import { race } from "bytekit/async";

// EN: Fastest response wins
// ES: La respuesta más rápida gana
const data = await race([
  fetch("/api/primary").then(r => r.json()),
  fetch("/api/mirror").then(r => r.json()),
  fetch("/api/cache").then(r => r.json()),
]);

// EN: If ALL fail, you get every error
// ES: Si TODAS fallan, obtienes todos los errores
try {
  await race([
    Promise.reject(new Error("Primary down")),
    Promise.reject(new Error("Mirror down")),
    Promise.reject(new Error("Cache down")),
  ]);
} catch (error) {
  if (error instanceof AggregateError) {
    console.log(error.errors);
    // ["Primary down", "Mirror down", "Cache down"]
  }
}

✅ allSettled

EN: Wraps Promise.allSettled to return a more convenient API. Instead of an array of { status, value/reason } objects, returns a structured { fulfilled, rejected } object where each entry includes the original index for traceability.

ES: Envuelve Promise.allSettled para retornar una API más conveniente. En lugar de un array de objetos { status, value/reason }, retorna un objeto estructurado { fulfilled, rejected } donde cada entrada incluye el índice original para trazabilidad.

Signature / Firma

function allSettled<T>(promises: Array<Promise<T>>): Promise<AllSettledResult<T>>
Parameter / Parámetro Type / Tipo Required Description (EN) Descripción (ES)
promises Array<Promise<T>> Promises to settle Promises a resolver

Example / Ejemplo

import { allSettled } from "bytekit/async";

const promises = [
  Promise.resolve("A"),               // index 0
  Promise.reject(new Error("fail")),   // index 1
  Promise.resolve("C"),               // index 2
];

const result = await allSettled(promises);

console.log(result.fulfilled);
// [{ value: "A", index: 0 }, { value: "C", index: 2 }]

console.log(result.rejected);
// [{ reason: Error("fail"), index: 1 }]

// EN: Quick check — did everything succeed?
// ES: Verificación rápida — ¿todo fue exitoso?
if (result.rejected.length === 0) {
  console.log("All operations succeeded!");
}

// EN: Report which operations failed by index
// ES: Reportar qué operaciones fallaron por índice
result.rejected.forEach(({ reason, index }) => {
  console.error(`Operation #${index} failed:`, reason);
});

🔔 debounceAsync

EN: Creates a debounced version of an async function. The function execution is delayed until delay milliseconds have elapsed since the last call. Supports leading and trailing edge execution. Returns a promise. Includes cancel() and flush() control methods.

ES: Crea una versión debounced de una función async. La ejecución se retrasa hasta que delay milisegundos hayan pasado desde la última llamada. Soporta ejecución en borde leading y trailing. Retorna una promise. Incluye métodos de control cancel() y flush().

Signature / Firma

function debounceAsync<TArgs extends any[], TReturn>(
  fn: AsyncFunction<TArgs, TReturn>,
  delay: number,
  options?: DebounceOptions
): DebouncedFunction<TArgs, TReturn>
Parameter / Parámetro Type / Tipo Required Description (EN) Descripción (ES)
fn AsyncFunction The async function to debounce La función async a debouncear
delay number Delay in milliseconds (must be ≥ 0) Delay en milisegundos (debe ser ≥ 0)
options DebounceOptions Leading/trailing edge config Configuración de borde leading/trailing

Debounce Timing Diagram / Diagrama de Temporización Debounce

Trailing (default):

Calls:    ▼   ▼   ▼               ▼       ▼
          │   │   │               │       │
Timeline: ─────────────────────────────────────▶
                      ▲                       ▲
                   Execute               Execute
                   (3rd args)            (5th args)
          ◄──delay──►               ◄──delay──►

Leading:

Calls:    ▼   ▼   ▼               ▼       ▼
          │   │   │               │       │
Timeline: ─────────────────────────────────────▶
          ▲                       ▲
       Execute                 Execute
       (1st args)              (4th args)

Return Value Methods / Métodos del Valor de Retorno

Method / Método Description (EN) Descripción (ES)
(...args) Invokes the debounced function, returns Promise<TReturn> Invoca la función debounced, retorna Promise<TReturn>
cancel() Cancels any pending execution and rejects pending promises Cancela cualquier ejecución pendiente y rechaza promises pendientes
flush() Immediately executes any pending call, returns Promise<TReturn | undefined> Ejecuta inmediatamente cualquier llamada pendiente, retorna Promise<TReturn | undefined>

Examples / Ejemplos

import { debounceAsync } from "bytekit/async";

// EN: Debounce a search API — only fires 300ms after last keystroke
// ES: Debounce de una API de búsqueda — solo dispara 300ms después de la última tecla
const searchAPI = async (query: string) => {
  const res = await fetch(`/api/search?q=${query}`);
  return res.json();
};

const debouncedSearch = debounceAsync(searchAPI, 300);

// EN: In an event handler — only the last call triggers the request
// ES: En un event handler — solo la última llamada dispara la petición
inputElement.addEventListener("input", async (e) => {
  try {
    const results = await debouncedSearch(e.target.value);
    renderResults(results);
  } catch {
    // Previous calls are rejected with "Debounced call cancelled"
  }
});

// EN: Leading edge — execute immediately on first call, ignore rest during delay
// ES: Borde leading — ejecutar inmediatamente en la primera llamada, ignorar el resto durante el delay
const debouncedSave = debounceAsync(saveDocument, 1000, { leading: true });

// EN: Manual control
// ES: Control manual
debouncedSearch.cancel();            // Cancel pending execution
const result = await debouncedSearch.flush(); // Force immediate execution

🚦 throttleAsync

EN: Creates a throttled version of an async function. The first call executes immediately. Subsequent calls within the interval are queued and the last one executes after the interval. Includes a cancel() method. With trailing: false, calls within the interval are rejected.

ES: Crea una versión throttled de una función async. La primera llamada se ejecuta inmediatamente. Las llamadas subsiguientes dentro del intervalo se encolan y la última se ejecuta después del intervalo. Incluye un método cancel(). Con trailing: false, las llamadas dentro del intervalo se rechazan.

Signature / Firma

function throttleAsync<TArgs extends any[], TReturn>(
  fn: AsyncFunction<TArgs, TReturn>,
  interval: number,
  options?: ThrottleOptions
): ThrottledFunction<TArgs, TReturn>
Parameter / Parámetro Type / Tipo Required Description (EN) Descripción (ES)
fn AsyncFunction The async function to throttle La función async a throttlear
interval number Minimum interval in ms between executions (must be ≥ 0) Intervalo mínimo en ms entre ejecuciones (debe ser ≥ 0)
options ThrottleOptions Trailing execution config Configuración de ejecución trailing

Throttle Timing Diagram / Diagrama de Temporización Throttle

Calls:    ▼   ▼ ▼ ▼               ▼   ▼
          │   │ │ │               │   │
Timeline: ─────────────────────────────────────▶
          ▲           ▲           ▲       ▲
       Execute     Execute     Execute  Execute
       (1st)      (4th/trail)  (5th)   (6th/trail)
          ◄──interval──►          ◄──int──►

Return Value Methods / Métodos del Valor de Retorno

Method / Método Description (EN) Descripción (ES)
(...args) Invokes the throttled function, returns Promise<TReturn> Invoca la función throttled, retorna Promise<TReturn>
cancel() Cancels any queued execution and rejects pending promises Cancela cualquier ejecución encolada y rechaza promises pendientes

Examples / Ejemplos

import { throttleAsync } from "bytekit/async";

// EN: Throttle scroll handler — at most once per 200ms
// ES: Throttle del handler de scroll — como máximo una vez cada 200ms
const reportScroll = async (position: number) => {
  await fetch("/api/analytics", {
    method: "POST",
    body: JSON.stringify({ scroll: position }),
  });
};

const throttledScroll = throttleAsync(reportScroll, 200);

window.addEventListener("scroll", () => {
  throttledScroll(window.scrollY);
});

// EN: API rate limiting — max 1 request per second
// ES: Rate limiting de API — máx 1 request por segundo
const throttledFetch = throttleAsync(
  (url: string) => fetch(url).then(r => r.json()),
  1000
);

// EN: Disable trailing — reject instead of queuing
// ES: Deshabilitar trailing — rechazar en lugar de encolar
const strictThrottle = throttleAsync(sendData, 500, { trailing: false });

// EN: Cancel queued execution
// ES: Cancelar ejecución encolada
throttledScroll.cancel();

🎯 PromisePool

EN: A reusable, class-based concurrency controller. Unlike parallel(), PromisePool is stateful (persists between run() calls), isolates individual task errors via onError without stopping the entire pool, and supports per-task timeouts. Ideal for batched workloads, API rate limiting, and long-running pipelines.

ES: Un controlador de concurrencia reutilizable basado en clases. A diferencia de parallel(), PromisePool es con estado (persiste entre llamadas run()), aísla errores individuales de tareas vía onError sin detener el pool, y soporta timeouts por tarea. Ideal para cargas de trabajo por lotes, rate limiting de APIs y pipelines de larga duración.

Signature / Firma

class PromisePool {
  constructor(options: PromisePoolOptions)
  run<T>(tasks: Array<() => Promise<T>>): Promise<T[]>
}
Parameter / Parámetro Type / Tipo Required Description (EN) Descripción (ES)
options.concurrency number Max tasks running simultaneously (minimum: 1) Máximo de tareas ejecutándose simult. (mínimo: 1)
options.timeout number Per-task timeout in ms. Must be > 0 Timeout por tarea en ms. Debe ser > 0
options.onError (error: Error, taskIndex: number) => void Called when a task fails. Pool continues. Llamado cuando una tarea falla. El pool continúa.
Throws / Lanza When / Cuándo
TypeError concurrency < 1, timeout <= 0, tasks is not an array, or any element is not a function / concurrency < 1, timeout <= 0, tasks no es un array, o algún elemento no es función
PoolTimeoutError A task exceeds the configured timeout / Una tarea supera el timeout configurado

PromisePool vs parallel() comparison / Comparación PromisePool vs parallel()

Feature / Característica parallel() PromisePool
API style Functional (one-shot) Class-based (reusable)
Reusable across batches
Per-task timeout
onError hook
Error stops pool ✅ (throws) ❌ (continues)
Suitable for One-time task lists Long-running pipelines, API rate limiting

Examples / Ejemplos

import { PromisePool, PoolTimeoutError } from "bytekit/async";

// EN: Limit concurrent file uploads to 3
// ES: Limitar uploads de archivos concurrentes a 3
const pool = new PromisePool({ concurrency: 3 });

const results = await pool.run(
  files.map((file) => () => uploadFile(file))
);

// EN: Reuse the pool for a second batch
// ES: Reutilizar el pool para un segundo lote
const moreResults = await pool.run(
  otherFiles.map((file) => () => uploadFile(file))
);
import { PromisePool, PoolTimeoutError } from "bytekit/async";

// EN: Per-task timeout with graceful error handling
// ES: Timeout por tarea con manejo graceful de errores
const pool = new PromisePool({
  concurrency: 2,
  timeout: 3000,
  onError(error, taskIndex) {
    if (error instanceof PoolTimeoutError) {
      console.warn(`Task ${taskIndex} timed out after 3s — skipping`);
    } else {
      console.error(`Task ${taskIndex} failed:`, error.message);
    }
    // Pool continues — remaining tasks still execute
  },
});

const data = await pool.run(
  endpoints.map((url) => () => fetch(url).then((r) => r.json()))
);
import { PromisePool } from "bytekit/async";

// EN: ApiClient integration — pool option limits all requests automatically
// ES: Integración con ApiClient — la opción pool limita todas las peticiones automáticamente
import { ApiClient } from "bytekit";

const api = new ApiClient({
  baseUrl: "https://api.example.com",
  pool: { concurrency: 2, timeout: 5000 },
});

// EN: At most 2 requests run concurrently, even if many are fired at once
// ES: Como máximo 2 peticiones corren concurrentemente, aunque se lancen muchas a la vez
const [users, posts, comments, tags] = await Promise.all([
  api.request("/users"),
  api.request("/posts"),
  api.request("/comments"),
  api.request("/tags"),
]);
// JavaScript (CommonJS) — same API, no type annotations
const { PromisePool, PoolTimeoutError } = require("bytekit/async");

const pool = new PromisePool({ concurrency: 3, timeout: 5000 });

pool.run([
  () => fetch("https://api.example.com/users").then((r) => r.json()),
  () => fetch("https://api.example.com/posts").then((r) => r.json()),
]).then((results) => {
  console.log("Results:", results);
}).catch((err) => {
  if (err instanceof PoolTimeoutError) {
    console.warn("A task timed out:", err.message);
  }
});

🧩 Combining Utilities / Combinando Utilidades

EN: The real power of the async toolkit is composing functions together. Here are common patterns:

ES: El verdadero poder del toolkit async es componer funciones juntas. Aquí hay patrones comunes:

Retry + Timeout / Reintento + Timeout

import { retry, timeout, TimeoutError, RetryError } from "bytekit/async";

// EN: Retry a fetch with a 5s timeout per attempt
// ES: Reintentar un fetch con timeout de 5s por intento
const data = await retry(
  () => timeout(fetch("/api/data").then(r => r.json()), 5000),
  { maxAttempts: 3, baseDelay: 1000 }
);

Parallel + Retry / Paralelo + Reintento

import { parallel, retry } from "bytekit/async";

// EN: Fetch multiple APIs in parallel, each with retry
// ES: Fetch de múltiples APIs en paralelo, cada una con reintento
const results = await parallel(
  endpoints.map(url => () => retry(() => fetch(url).then(r => r.json()))),
  { concurrency: 3 }
);

Sequential + Timeout / Secuencial + Timeout

import { sequential, timeout } from "bytekit/async";

// EN: Run migration steps sequentially, each with a 30s timeout
// ES: Ejecutar pasos de migración secuencialmente, cada uno con timeout de 30s
const results = await sequential([
  () => timeout(migrateUsers(), 30_000),
  () => timeout(migratePosts(), 30_000),
  () => timeout(migrateComments(), 30_000),
]);

Debounce + Retry / Debounce + Reintento

import { debounceAsync, retry } from "bytekit/async";

// EN: Debounce a search with automatic retries on failure
// ES: Debounce de búsqueda con reintentos automáticos en fallos
const resilientSearch = debounceAsync(
  (query: string) => retry(() => searchAPI(query), { maxAttempts: 2 }),
  300
);

PromisePool + Retry / Pool + Reintento

import { PromisePool, PoolTimeoutError, retry } from "bytekit/async";

// EN: Fetch multiple URLs in parallel, each with automatic retry on failure.
// PromisePool limits concurrency; retry handles transient errors per task.
// ES: Fetch de múltiples URLs en paralelo, cada una con reintento automático.
// PromisePool limita la concurrencia; retry maneja errores transitorios por tarea.
const pool = new PromisePool({
  concurrency: 3,
  onError: (err, i) => console.warn(`Task ${i} ultimately failed:`, err.message),
});

const results = await pool.run(
  urls.map((url) => () =>
    retry(() => fetch(url).then((r) => r.json()), {
      maxAttempts: 3,
      baseDelay: 500,
    })
  )
);

📊 Comparison Table / Tabla Comparativa

Feature sleep timeout withTimeout retry parallel sequential race allSettled debounceAsync throttleAsync PromisePool RequestQueue RequestBatcher
AbortSignal
Backoff
Concurrency
Error grouping
Cancellation
Result chaining
Per-task timeout
Error isolation
Reusable
Priority lanes
Deduplication
Time-window batch

🔄 RequestQueue — Priority-aware Concurrency Queue

EN: At most concurrency tasks run simultaneously. Three lanes — high, normal, low — control execution order. Supports external AbortSignal cancellation per task.

ES: Como máximo concurrency tareas se ejecutan simultáneamente. Tres carriles controlan el orden de ejecución. Soporta cancelación externa por tarea vía AbortSignal.

Import

import { RequestQueue, QueueAbortError } from "bytekit/async";
import type { RequestQueueOptions, AddOptions, QueuePriority } from "bytekit/async";

API Table / Tabla de API

Member Type Description (EN) Descripción (ES)
constructor(options) (RequestQueueOptions) => RequestQueue Creates queue. Throws TypeError if concurrency < 1. Crea la cola. Lanza TypeError si concurrency < 1.
add<T>(task, options?) (task, AddOptions?) => Promise<T> Enqueues task. Task receives internal AbortSignal. Encola tarea. La tarea recibe una AbortSignal interna.
flush() () => Promise<void> Resolves when all queued + running tasks settle. Resuelve cuando todas las tareas en cola y en ejecución terminan.
size number (getter) Tasks waiting to start. Tareas esperando comenzar.
running number (getter) Tasks currently executing. Tareas ejecutándose actualmente.
pending number (getter) size + running. size + running.

RequestQueueOptions

Option Type Default Description
concurrency number Max simultaneous tasks. Required. Minimum: 1.
maxQueueSize number Infinity Maximum number of tasks allowed in the waiting queue. add() rejects with TypeError when the limit is reached.
onError (error: Error, id: string) => void Called when task rejects. Queue continues.

AddOptions

Option Type Default Description
priority "high" | "normal" | "low" "normal" Execution lane. High dequeues before normal; normal before low.
signal AbortSignal External cancellation. Task rejects with QueueAbortError if aborted before start.

QueueAbortError

class QueueAbortError extends Error {
  name: "QueueAbortError"
}

Thrown when a queued task is cancelled via AddOptions.signal.

Usage Examples / Ejemplos de Uso

TypeScript / ESM

import { RequestQueue, QueueAbortError } from "bytekit/async";

// ── Concurrency limit ──
const queue = new RequestQueue({
  concurrency: 3,
  onError: (err, id) => console.warn(`Task ${id} failed:`, err.message),
});

const results = await Promise.all(
  ids.map((id) => queue.add((signal) => fetch(`/api/${id}`, { signal })))
);

// ── Priority ordering ──
const pq = new RequestQueue({ concurrency: 1 });
let unlock: () => void;
const blocker = pq.add((_s) => new Promise<void>((r) => { unlock = r; }));

pq.add((_s) => doBackgroundSync(), { priority: "low" });
pq.add((_s) => doAnalytics(),       { priority: "high" }); // runs first

unlock!();
await blocker;
await pq.flush(); // waits for all to settle

// ── Cancellation via AbortSignal ──
const controller = new AbortController();
pq.add(
  (signal) => fetch("/api/search", { signal }),
  { signal: controller.signal }
).catch((err) => {
  if (err instanceof QueueAbortError) console.log("Cancelled!");
});
controller.abort();

JavaScript (CJS)

const { RequestQueue, QueueAbortError } = require("bytekit/async");

const queue = new RequestQueue({ concurrency: 5 });

queue
  .add(() => fetch("/api/data").then((r) => r.json()))
  .then((data) => console.log(data))
  .catch((err) => {
    if (err instanceof QueueAbortError) {
      console.log("Cancelled");
    }
  });

🗂️ RequestBatcher — Time-window Request Deduplication

EN: Coalesces same-key HTTP requests within a configurable time window into a single fetcher call. All callers receive the same response. Supports fixed and sliding windows, maxSize early flush, and custom key functions.

ES: Coalece requests HTTP con la misma clave dentro de una ventana de tiempo configurable en una sola llamada al fetcher. Todos los callers reciben la misma respuesta. Soporta ventanas fijas y deslizantes, flush temprano por maxSize, y funciones de clave personalizadas.

Import

import { RequestBatcher } from "bytekit/async";
import type { BatchOptions } from "bytekit/async";

API Table / Tabla de API

Member Type Description (EN) Descripción (ES)
constructor(options) (BatchOptions) => RequestBatcher Creates batcher. Throws TypeError for invalid options. Crea el batcher. Lanza TypeError para opciones inválidas.
add<T>(url, init, fetcher) (string, RequestInit, fetcher) => Promise<T> Adds request to window. Returns promise resolving to response. Agrega request a la ventana. Retorna promise que resuelve con la respuesta.
flush() () => Promise<void> Forces immediate dispatch of all pending batches. Fuerza el despacho inmediato de todos los batches pendientes.
pendingCount number (getter) Total requests pending across all bucket keys. Total de requests pendientes en todos los buckets.

BatchOptions

Option Type Default Description
windowMs number Collection window in ms. Required. Must be > 0.
maxSize number Infinity Max requests per batch. Flushes early when reached. Must be ≥ 1.
maxPending number Infinity Maximum total pending requests across all buckets. add() rejects with TypeError when the limit is reached.
sliding boolean false Sliding window: timer resets on each new request.
keyFn (url, init) => string "METHOD:url:body" Custom deduplication key function.

Default Key Function / Función de Clave por Defecto

The default key combines method, URL, and serialized body:

// key = "GET:/api/users:"     (no body)
// key = "POST:/api/items:{"id":1}"

Requests with different bodies get different keys and dispatch independently.

Usage Examples / Ejemplos de Uso

TypeScript / ESM

import { RequestBatcher } from "bytekit/async";

// ── Fixed window — 5 callers → 1 fetch ──
const batcher = new RequestBatcher({ windowMs: 50 });

const [a, b, c] = await Promise.all([
  batcher.add("/api/users", { method: "GET" }, fetch),
  batcher.add("/api/users", { method: "GET" }, fetch),
  batcher.add("/api/users", { method: "GET" }, fetch),
]);
await batcher.flush(); // dispatch immediately instead of waiting 50ms

// ── Sliding window — timer resets on each add ──
const sliding = new RequestBatcher({ windowMs: 200, sliding: true });

sliding.add("/api/search", { method: "GET" }, fetch); // timer: t+200ms
await sleep(150); // only 150ms elapsed
sliding.add("/api/search", { method: "GET" }, fetch); // timer resets: t+350ms
// Dispatch fires 200ms after the LAST add

// ── maxSize — early flush at N requests ──
const bounded = new RequestBatcher({ windowMs: 10_000, maxSize: 5 });
// Dispatches immediately when the 5th request is added, no need to wait 10s

// ── Custom key — group by endpoint category ──
const custom = new RequestBatcher({
  windowMs: 100,
  keyFn: (url) => new URL(url).pathname.split("/")[1], // group by path segment
});

JavaScript (CJS)

const { RequestBatcher } = require("bytekit/async");

const batcher = new RequestBatcher({ windowMs: 50, maxSize: 20 });

// All three calls share the same key → one fetch
Promise.all([
  batcher.add("/api/items", { method: "GET" }, fetch),
  batcher.add("/api/items", { method: "GET" }, fetch),
  batcher.add("/api/items", { method: "GET" }, fetch),
]).then(([r1, r2, r3]) => {
  console.log("All got same response:", r1 === r2); // true
});

await batcher.flush();

ApiClient Integration / Integración con ApiClient

import { ApiClient } from "bytekit";

// Queue: max 5 concurrent requests
const client = new ApiClient({
  baseUrl: "https://api.example.com",
  queue: { concurrency: 5 },
});

// Batch: same-URL GET requests within 100ms share one fetch
const batchedClient = new ApiClient({
  baseUrl: "https://api.example.com",
  batch: { windowMs: 100 },
});

// Existing pool option still works (queue takes priority if both are set)
const legacyClient = new ApiClient({
  baseUrl: "https://api.example.com",
  pool: { concurrency: 3 },
});


Véase También / See Also

  • RetryPolicy — Higher-level policy-based retry management / Gestión de reintentos basada en políticas de alto nivel
  • CircuitBreaker — Circuit breaker pattern for fault tolerance / Patrón circuit breaker para tolerancia a fallos
  • RateLimiter — Token-bucket rate limiting / Rate limiting con token-bucket
  • PollingHelper — Periodic polling with async tasks / Polling periódico con tareas async
  • StreamingHelper — Streaming data processing / Procesamiento de datos en streaming
  • RequestDeduplicator — Deduplication of concurrent requests / Deduplicación de peticiones concurrentes
  • ErrorBoundary — Error boundary pattern / Patrón de límite de errores

Clone this wiki locally