Advanced promise management utilities providing singleton promises, promise locks, multi-promise handling, and async control flow patterns.
Create promise functions that ensure only one instance runs at a time.
interface SingletonPromiseReturn<T> {
(): Promise<T>;
/**
* Reset current staled promise.
* Await it to have proper shutdown.
*/
reset: () => Promise<void>;
}
/**
* Create singleton promise function
* @param fn - Function that returns a promise
* @returns Singleton promise function with reset capability
*/
function createSingletonPromise<T>(fn: () => Promise<T>): SingletonPromiseReturn<T>;Usage Examples:
import { createSingletonPromise } from "@antfu/utils";
// API call that should only happen once at a time
const fetchUser = createSingletonPromise(async () => {
const response = await fetch('/api/user');
return response.json();
});
// Multiple calls will share the same promise
const user1Promise = fetchUser(); // Starts API call
const user2Promise = fetchUser(); // Reuses same promise
const user3Promise = fetchUser(); // Reuses same promise
console.log(user1Promise === user2Promise); // true
// Reset to allow new calls
await fetchUser.reset();
const newUserPromise = fetchUser(); // Fresh API call
// Heavy computation singleton
const expensiveComputation = createSingletonPromise(async () => {
console.log('Starting expensive computation...');
await new Promise(resolve => setTimeout(resolve, 5000));
return 'computed result';
});
// Database connection singleton
const connectToDatabase = createSingletonPromise(async () => {
console.log('Connecting to database...');
const connection = await database.connect();
return connection;
});Async timing utilities for delays and scheduling.
/**
* Promised `setTimeout`
* @param ms - Milliseconds to wait
* @param callback - Optional callback to execute after delay
* @returns Promise that resolves after the specified time
*/
function sleep(ms: number, callback?: Fn<any>): Promise<void>;Usage Examples:
import { sleep } from "@antfu/utils";
// Simple delay
async function example() {
console.log('Starting...');
await sleep(1000); // Wait 1 second
console.log('Done!');
}
// Delay with callback
await sleep(2000, () => {
console.log('This runs after 2 seconds');
});
// Retry logic with exponential backoff
async function retryWithBackoff<T>(
fn: () => Promise<T>,
maxRetries: number = 3
): Promise<T> {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s, 8s...
console.log(`Retrying in ${delay}ms...`);
await sleep(delay);
}
}
throw new Error('Max retries exceeded');
}
// Animation timing
async function fadeIn(element: HTMLElement) {
element.style.opacity = '0';
element.style.display = 'block';
for (let opacity = 0; opacity <= 1; opacity += 0.1) {
element.style.opacity = opacity.toString();
await sleep(50); // 50ms between steps
}
}
// Rate limiting
async function rateLimitedRequests(urls: string[]) {
const results = [];
for (const url of urls) {
const response = await fetch(url);
results.push(await response.json());
await sleep(100); // 100ms between requests
}
return results;
}Coordinate async operations with promise-based locking mechanisms.
interface PromiseLock {
/**
* Run a function with the lock
* @param fn - Async function to run
* @returns Promise resolving to the function's result
*/
run<T = void>(fn: () => Promise<T>): Promise<T>;
/**
* Wait for all running tasks to complete
* @returns Promise that resolves when all tasks are done
*/
wait(): Promise<void>;
/**
* Check if any tasks are currently running
* @returns True if tasks are running
*/
isWaiting(): boolean;
/**
* Clear all locks (does not wait for completion)
*/
clear(): void;
}
/**
* Create a promise lock for coordinating async operations
* @returns Promise lock instance
*/
function createPromiseLock(): PromiseLock;Usage Examples:
import { createPromiseLock } from "@antfu/utils";
// Resource access coordination
const dbLock = createPromiseLock();
async function updateUser(userId: string, data: any) {
return dbLock.run(async () => {
const user = await db.findUser(userId);
const updated = { ...user, ...data };
return db.saveUser(updated);
});
}
// File system operations
const fileLock = createPromiseLock();
async function writeToFile(filename: string, content: string) {
return fileLock.run(async () => {
await fs.writeFile(filename, content);
console.log(`Written to ${filename}`);
});
}
// Background task coordination
const backgroundTasks = createPromiseLock();
// Start some background work
backgroundTasks.run(async () => {
await processImages();
});
backgroundTasks.run(async () => {
await generateReports();
});
// Wait for all background tasks to complete before shutdown
async function gracefulShutdown() {
console.log('Waiting for background tasks...');
await backgroundTasks.wait();
console.log('All tasks completed, shutting down');
}
// Check if work is in progress
if (backgroundTasks.isWaiting()) {
console.log('Background tasks are still running');
}Create promises with externally accessible resolve and reject methods.
interface ControlledPromise<T = void> extends Promise<T> {
resolve: (value: T | PromiseLike<T>) => void;
reject: (reason?: any) => void;
}
/**
* Return a Promise with `resolve` and `reject` methods
* @returns Promise with exposed resolve/reject methods
*/
function createControlledPromise<T>(): ControlledPromise<T>;Usage Examples:
import { createControlledPromise } from "@antfu/utils";
// Manual promise control
const manualPromise = createControlledPromise<string>();
// Resolve from another context
setTimeout(() => {
manualPromise.resolve('Hello from timeout!');
}, 2000);
const result = await manualPromise; // Waits for manual resolution
// Event-driven promises
function waitForEvent(element: EventTarget, eventName: string) {
const promise = createControlledPromise<Event>();
const handler = (event: Event) => {
element.removeEventListener(eventName, handler);
promise.resolve(event);
};
element.addEventListener(eventName, handler);
return promise;
}
// Usage: await waitForEvent(button, 'click');
// Queue management
class TaskQueue {
private pending = new Map<string, ControlledPromise<any>>();
async waitForTask(taskId: string) {
if (!this.pending.has(taskId)) {
this.pending.set(taskId, createControlledPromise());
}
return this.pending.get(taskId)!;
}
completeTask(taskId: string, result: any) {
const promise = this.pending.get(taskId);
if (promise) {
promise.resolve(result);
this.pending.delete(taskId);
}
}
failTask(taskId: string, error: any) {
const promise = this.pending.get(taskId);
if (promise) {
promise.reject(error);
this.pending.delete(taskId);
}
}
}
// Communication between components
const communicationBridge = createControlledPromise<{ type: string; data: any }>();
// Component A waits for message
const message = await communicationBridge;
console.log('Received:', message);
// Component B sends message
communicationBridge.resolve({ type: 'user-action', data: { userId: 123 } });Advanced utilities for handling multiple promises with transformation and filtering capabilities.
interface POptions {
/**
* How many promises are resolved at the same time.
*/
concurrency?: number | undefined;
}
interface PInstance<T = any> extends Promise<Awaited<T>[]> {
/** Add more promises to the collection */
add(...args: (T | Promise<T>)[]): void;
/** Transform resolved values */
map<U>(fn: (value: Awaited<T>, index: number) => U): PInstance<Promise<U>>;
/** Filter resolved values */
filter(fn: (value: Awaited<T>, index: number) => boolean | Promise<boolean>): PInstance<Promise<T>>;
/** Execute function for each resolved value */
forEach(fn: (value: Awaited<T>, index: number) => void): Promise<void>;
/** Reduce resolved values */
reduce<U>(fn: (previousValue: U, currentValue: Awaited<T>, currentIndex: number, array: Awaited<T>[]) => U, initialValue: U): Promise<U>;
/** Clear all promises */
clear(): void;
}
/**
* Utility for managing multiple promises
* @param items - Initial items to process
* @param options - Configuration options
* @returns PInstance for chaining operations
*/
function p<T = any>(items?: Iterable<T>, options?: POptions): PInstance<T>;Usage Examples:
import { p } from "@antfu/utils";
// Basic promise chaining
const items = [1, 2, 3, 4, 5];
const results = await p(items)
.map(async i => {
await sleep(100); // Simulate async work
return i * 3;
})
.filter(async i => {
await sleep(50); // Simulate async filtering
return i % 2 === 0;
});
// Results: [6, 12]
// Concurrency control
const urls = ['url1', 'url2', 'url3', 'url4', 'url5'];
const responses = await p(urls, { concurrency: 2 })
.map(async url => {
const response = await fetch(url);
return response.json();
});
// Dynamic promise management
const promiseManager = p<string>();
// Add promises dynamically
promiseManager.add(
fetch('/api/users').then(r => r.text()),
fetch('/api/posts').then(r => r.text()),
Promise.resolve('static-data')
);
// Process when ready
const data = await promiseManager.map(text => JSON.parse(text));
// Batch processing with error handling
async function processBatch(items: any[]) {
return p(items)
.map(async item => {
try {
return await processItem(item);
} catch (error) {
console.error(`Failed to process item:`, item, error);
return null;
}
})
.filter(result => result !== null);
}
// Data pipeline
const pipeline = p(['data1', 'data2', 'data3'])
.map(async data => await validate(data))
.filter(async data => await isValid(data))
.map(async data => await transform(data))
.reduce(async (acc, item) => {
acc.push(await finalize(item));
return acc;
}, []);
const finalResult = await pipeline;