Async Utilities Examples
Comprehensive async/promise utilities for modern JavaScript applications.
Overview
The async category provides utilities for managing promises, concurrency control, retries, timeouts, and other asynchronous patterns.
attempt
Execute a function with advanced error handling and retry logic.
import { attempt } from '@vielzeug/toolkit';
// Basic usage
const unreliableFunction = async () => {
if (Math.random() < 0.7) throw new Error('Random failure');
return 'Success!';
};
const result = await attempt(unreliableFunction, {
retries: 3,
timeout: 5000,
silent: false,
});
// With identifier for logging
await attempt(fetchUserData, {
identifier: 'fetchUserData',
retries: 5,
timeout: 10000,
});defer
Create a promise with externally accessible resolve/reject methods.
import { defer } from '@vielzeug/toolkit';
// Basic usage
const deferred = defer<string>();
setTimeout(() => {
deferred.resolve('Done!');
}, 1000);
const result = await deferred.promise; // 'Done!'
// Event-based workflow
const eventPromise = defer<Event>();
element.addEventListener(
'click',
(e) => {
eventPromise.resolve(e);
},
{ once: true },
);
const clickEvent = await eventPromise.promise;delay
Delay the execution of a function by a specified time.
import { delay } from '@vielzeug/toolkit';
// Basic usage
const log = () => console.log('Hello, world!');
delay(log, 1000); // Logs after 1 second
// With async function
const fetchData = async () => fetch('/api/data');
const result = await delay(fetchData, 500);
// Custom delay
await delay(() => processData(), 2000);parallel
Process an array with controlled parallelism.
import { parallel } from '@vielzeug/toolkit';
// Process 3 items at a time
const urls = ['url1', 'url2', 'url3', 'url4', 'url5'];
const results = await parallel(3, urls, async (url) => {
const response = await fetch(url);
return response.json();
});
// With AbortSignal
const controller = new AbortController();
const results = await parallel(5, items, async (item) => processItem(item), controller.signal);pool
Create a promise pool for rate limiting concurrent operations.
import { pool } from '@vielzeug/toolkit';
// Create pool with max 3 concurrent requests
const requestPool = pool(3);
const results = await Promise.all([
requestPool(() => fetch('/api/1')),
requestPool(() => fetch('/api/2')),
requestPool(() => fetch('/api/3')),
requestPool(() => fetch('/api/4')), // Waits for a slot
requestPool(() => fetch('/api/5')), // Waits for a slot
]);
// Reusable pool
const apiPool = pool(5);
async function fetchUser(id: number) {
return apiPool(() => fetch(`/api/users/${id}`).then((r) => r.json()));
}predict
Add timeout to async operations with AbortSignal support.
import { predict } from '@vielzeug/toolkit';
// Basic usage
const result = await predict(
async (signal) => {
const response = await fetch('/api/data', { signal });
return response.json();
},
{ timeout: 5000 },
);
// With custom AbortSignal
const controller = new AbortController();
const result = await predict(async (signal) => longRunningTask(signal), { timeout: 10000, signal: controller.signal });
// Combine with retry
import { retry } from '@vielzeug/toolkit';
const result = await retry(() => predict((signal) => fetchData(signal), { timeout: 5000 }), { times: 3, delay: 1000 });queue
Create a task queue with concurrency control and monitoring.
import { queue } from '@vielzeug/toolkit';
// Basic usage
const taskQueue = queue({ concurrency: 2 });
taskQueue.add(() => fetch('/api/1'));
taskQueue.add(() => fetch('/api/2'));
taskQueue.add(() => fetch('/api/3'));
// Wait for all tasks
await taskQueue.onIdle();
// Monitor queue
console.log(taskQueue.size); // Pending tasks
console.log(taskQueue.pending); // Currently running
// Clear remaining tasks
taskQueue.clear();
// Task queue with results
const results: string[] = [];
const q = queue({ concurrency: 3 });
for (const url of urls) {
const result = await q.add(() => fetch(url).then((r) => r.text()));
results.push(result);
}race
Race promises with a guaranteed minimum delay (better UX for loading states).
import { race } from '@vielzeug/toolkit';
// Show loading spinner for at least 500ms
const data = await race(fetchQuickData(), 500);
// Prevents flickering for fast operations
const result = await race(
[fetch('/api/1'), fetch('/api/2')],
1000, // Ensure at least 1 second
);
// Use case: Better UX
async function loadUserProfile(userId: string) {
// Even if data loads in 50ms, show spinner for at least 300ms
const user = await race(fetchUser(userId), 300);
return user;
}retry
Retry async operations with exponential backoff.
import { retry } from '@vielzeug/toolkit';
// Basic retry
const result = await retry(() => fetchData(), { times: 3, delay: 1000 });
// Exponential backoff
const result = await retry(() => unreliableAPICall(), {
times: 5,
delay: 500,
backoff: 2, // 500ms, 1000ms, 2000ms, 4000ms, 8000ms
});
// With AbortSignal
const controller = new AbortController();
const result = await retry(() => fetchData(), {
times: 3,
delay: 1000,
signal: controller.signal,
});
// Custom backoff function
const result = await retry(() => fetchData(), {
times: 4,
delay: 1000,
backoff: (attempt, delay) => delay * attempt, // Linear backoff
});sleep
Create a promise that resolves after a specified time.
import { sleep } from '@vielzeug/toolkit';
// Basic usage
await sleep(1000); // Wait 1 second
console.log('Done waiting');
// In async workflows
async function processWithDelay() {
console.log('Starting...');
await sleep(2000);
console.log('Processing...');
await sleep(1000);
console.log('Done!');
}
// Rate limiting
for (const item of items) {
await processItem(item);
await sleep(100); // 100ms between each item
}waitFor
Poll for a condition to become true.
import { waitFor } from '@vielzeug/toolkit';
// Wait for DOM element
await waitFor(() => document.querySelector('#myElement') !== null, { timeout: 5000, interval: 100 });
// Wait for API to be ready
await waitFor(
async () => {
try {
const res = await fetch('/api/health');
return res.ok;
} catch {
return false;
}
},
{ timeout: 30000, interval: 1000 },
);
// Wait for condition with cleanup
const controller = new AbortController();
setTimeout(() => controller.abort(), 10000);
await waitFor(() => window.myGlobal !== undefined, {
timeout: 15000,
interval: 200,
signal: controller.signal,
});Real-World Examples
API Request with Retry and Timeout
import { retry, predict } from '@vielzeug/toolkit';
async function fetchWithRetry(url: string) {
return retry(
() =>
predict(
async (signal) => {
const response = await fetch(url, { signal });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
},
{ timeout: 5000 },
),
{ times: 3, delay: 1000, backoff: 2 },
);
}Batch Processing with Rate Limiting
import { pool, sleep } from '@vielzeug/toolkit';
async function processBatch<T, R>(
items: T[],
processor: (item: T) => Promise<R>,
{ batchSize = 10, delayBetweenBatches = 1000, concurrency = 3 } = {},
) {
const requestPool = pool(concurrency);
const results: R[] = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = await Promise.all(batch.map((item) => requestPool(() => processor(item))));
results.push(...batchResults);
if (i + batchSize < items.length) {
await sleep(delayBetweenBatches);
}
}
return results;
}Task Queue with Progress Tracking
import { queue } from '@vielzeug/toolkit';
async function processTasksWithProgress<T>(
tasks: Array<() => Promise<T>>,
onProgress?: (completed: number, total: number) => void,
) {
const taskQueue = queue({ concurrency: 5 });
const results: T[] = [];
let completed = 0;
for (const task of tasks) {
taskQueue.add(async () => {
const result = await task();
results.push(result);
completed++;
onProgress?.(completed, tasks.length);
});
}
await taskQueue.onIdle();
return results;
}Polling with Timeout
import { waitFor } from '@vielzeug/toolkit';
async function waitForJobCompletion(jobId: string) {
await waitFor(
async () => {
const status = await fetch(`/api/jobs/${jobId}`).then((r) => r.json());
return status.completed === true;
},
{ timeout: 60000, interval: 2000 },
);
return fetch(`/api/jobs/${jobId}/result`).then((r) => r.json());
}