-
Notifications
You must be signed in to change notification settings - Fork 1
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.
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 {
// 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";| 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 |
EN: All error classes extend the native
Errorclass and maintain proper stack traces viaError.captureStackTrace(V8 engines).ES: Todas las clases de error extienden la clase nativa
Errory mantienen stack traces correctos medianteError.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 |
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);
}
}type BackoffStrategy =
| "linear" // delay = baseDelay × attempt
| "exponential" // delay = baseDelay × 2^(attempt - 1)
| ((attempt: number) => number); // Custom functionEN: 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.
type Task<T> = () => Promise<T>;
type SequentialTask<T> = (previousResult?: any) => Promise<T>;type AsyncFunction<TArgs extends any[], TReturn> = (...args: TArgs) => Promise<TReturn>;| 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 |
| Property / Propiedad | Type / Tipo | Default | Description (EN) | Descripción (ES) |
|---|---|---|---|---|
concurrency |
number |
undefined (unlimited) |
Max concurrent task executions | Máximo de ejecuciones concurrentes |
| 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 |
interface AllSettledResult<T> {
fulfilled: Array<{ value: T; index: number }>;
rejected: Array<{ reason: unknown; index: number }>;
}| 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) |
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
}| 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 |
interface ThrottledFunction<TArgs extends any[], TReturn> {
(...args: TArgs): Promise<TReturn>; // Call the throttled function
cancel(): void; // Cancel any queued execution
}| 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 |
EN: Creates a Promise-based delay with optional
AbortSignalcancellation. 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.
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 |
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 immediatelyEN: 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).
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 |
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"
);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.
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 |
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);EN: Retries a failed async operation with configurable backoff strategy, delay caps, selective retry predicates, and
AbortSignalcancellation. 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.
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
|
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)
┌──────────────┐
│ 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())
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),
});EN: Executes multiple async tasks in parallel. Without concurrency limit, uses
Promise.allfor 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.allpara 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.
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) |
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
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 }
);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.
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 |
Task 1 Task 2 Task 3
┌──────┐ result ┌──────┐ result ┌──────┐
│ fn() │────────▶│ fn(r) │────────▶│ fn(r) │──▶ [r1, r2, r3]
└──────┘ └──────┘ └──────┘
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 tasksEN: An enhanced
Promise.raceimplementation. If any promise fulfills, resolves with the first value. If all promises reject, rejects with anAggregateErrorcontaining every rejection reason — unlike nativePromise.racewhich 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 unAggregateErrorque contiene todas las razones de rechazo — a diferencia delPromise.racenativo que solo rechaza con el primer error.
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 |
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!
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"]
}
}EN: Wraps
Promise.allSettledto 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.allSettledpara 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.
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 |
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);
});EN: Creates a debounced version of an async function. The function execution is delayed until
delaymilliseconds have elapsed since the last call. Supportsleadingandtrailingedge execution. Returns a promise. Includescancel()andflush()control methods.ES: Crea una versión debounced de una función async. La ejecución se retrasa hasta que
delaymilisegundos hayan pasado desde la última llamada. Soporta ejecución en bordeleadingytrailing. Retorna una promise. Incluye métodos de controlcancel()yflush().
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 |
Trailing (default):
Calls: ▼ ▼ ▼ ▼ ▼
│ │ │ │ │
Timeline: ─────────────────────────────────────▶
▲ ▲
Execute Execute
(3rd args) (5th args)
◄──delay──► ◄──delay──►
Leading:
Calls: ▼ ▼ ▼ ▼ ▼
│ │ │ │ │
Timeline: ─────────────────────────────────────▶
▲ ▲
Execute Execute
(1st args) (4th args)
| 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>
|
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 executionEN: 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. Withtrailing: 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(). Contrailing: false, las llamadas dentro del intervalo se rechazan.
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 |
Calls: ▼ ▼ ▼ ▼ ▼ ▼
│ │ │ │ │ │
Timeline: ─────────────────────────────────────▶
▲ ▲ ▲ ▲
Execute Execute Execute Execute
(1st) (4th/trail) (5th) (6th/trail)
◄──interval──► ◄──int──►
| 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 |
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();EN: A reusable, class-based concurrency controller. Unlike
parallel(),PromisePoolis stateful (persists betweenrun()calls), isolates individual task errors viaonErrorwithout 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(),PromisePooles con estado (persiste entre llamadasrun()), aísla errores individuales de tareas víaonErrorsin 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.
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 |
| 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 |
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);
}
});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:
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 }
);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 }
);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),
]);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
);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,
})
)
);| 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 | — | — | — | — | — | — | — | — | — | — | — | — | ✅ |
EN: At most
concurrencytasks run simultaneously. Three lanes —high,normal,low— control execution order. Supports externalAbortSignalcancellation per task.ES: Como máximo
concurrencytareas se ejecutan simultáneamente. Tres carriles controlan el orden de ejecución. Soporta cancelación externa por tarea víaAbortSignal.
import { RequestQueue, QueueAbortError } from "bytekit/async";
import type { RequestQueueOptions, AddOptions, QueuePriority } from "bytekit/async";| 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. |
| 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. |
| 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. |
class QueueAbortError extends Error {
name: "QueueAbortError"
}Thrown when a queued task is cancelled via AddOptions.signal.
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();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");
}
});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,
maxSizeearly 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 { RequestBatcher } from "bytekit/async";
import type { BatchOptions } from "bytekit/async";| 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. |
| 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. |
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.
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
});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();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 },
});- 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