@nhlinh123/single-flight
v1.0.0
Published
A lightweight, zero-dependency TypeScript library implementing the **Single-Flight pattern** (in-flight deduplication). It prevents duplicate concurrent requests for the same operation by sharing a single execution.
Readme
Single-Flight Library
A lightweight, zero-dependency TypeScript library implementing the Single-Flight pattern (in-flight deduplication). It prevents duplicate concurrent requests for the same operation by sharing a single execution.
Ideal for:
- Preventing duplicate API calls (e.g., token refresh, cache warming).
- Deduplicating expensive async operations.
- Optimizing high-concurrency applications.
Features
- 🚀 Universal Support: Works with Promises and RxJS Observables.
- 🔑 Key-based Deduplication: Requests with the same key share the execution.
- 🧹 Auto Cleanup: Automatically removes operations from the registry when they complete or error.
- 🎨 Decorators: Easy-to-use TypeScript decorators for class methods.
- ⚛️ Framework Ready: Built-in adapters for React (Hooks) and Angular (Service).
- 🌲 Tree-Shakeable: Import only what you need.
- 📦 Tiny: Core logic is < 5KB.
Installation
npm install @nhlinh123/single-flight rxjs(Note: rxjs is a peer dependency. react and @angular/core are optional peer dependencies if you use those specific adapters.)
Usage
1. Core Class Usage
The SingleFlight class is the heart of the library.
import { SingleFlight } from '@nhlinh123/single-flight';
const sf = new SingleFlight();
// --- Promise Example ---
const fetchUser = (id: string) => fetch(`/api/users/${id}`).then(r => r.json());
// These two calls will result in ONLY ONE network request
const user1 = sf.executeAsync('user-123', () => fetchUser('123'));
const user2 = sf.executeAsync('user-123', () => fetchUser('123'));
await Promise.all([user1, user2]);
// --- Observable Example ---
import { HttpClient } from '@angular/common/http'; // or any RxJS source
// These two subscriptions share the same source execution
sf.execute('settings', () => http.get('/api/settings')).subscribe();
sf.execute('settings', () => http.get('/api/settings')).subscribe();2. Functional Wrappers
For quick, one-off usage without instantiating a class.
import { singleFlightAsync, singleFlight } from '@nhlinh123/single-flight';
// Promise
await singleFlightAsync('my-key', () => myAsyncOperation());
// Observable
singleFlight('my-key', () => myObservable$).subscribe();3. Decorators
Automatically deduplicate method calls based on arguments.
import { SingleFlightDecorator } from '@nhlinh123/single-flight';
class UserService {
// Generates key "user-123" when calling getUser('123')
@SingleFlightDecorator('user-{0}')
async getUser(id: string) {
console.log('Fetching user...');
return fetch(`/users/${id}`).then(r => r.json());
}
}
const service = new UserService();
// "Fetching user..." logs only once
service.getUser('1');
service.getUser('1');4. React Hook
Use useSingleFlight to manage async state with deduplication in React components.
import { useSingleFlight } from '@nhlinh123/single-flight';
const UserProfile = ({ userId }) => {
const { data, loading, error, refetch } = useSingleFlight(
`user-${userId}`,
() => fetchUser(userId)
);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>User: {data.name}</div>;
};5. Angular Service
Inject SingleFlightService to share a global deduplication registry across your app.
import { Component, OnInit } from '@angular/core';
import { SingleFlightService } from '@nhlinh123/single-flight';
@Component({ ... })
export class MyComponent implements OnInit {
constructor(private sf: SingleFlightService, private http: HttpClient) {}
ngOnInit() {
// Share this request with any other component requesting 'config'
this.sf.execute('config', () => this.http.get('/api/config'))
.subscribe(config => console.log(config));
}
}API Reference
SingleFlight Class
executeAsync<T>(key: string, fn: () => Promise<T>): Promise<T>Executes a Promise-based operation. If an operation withkeyis already in progress, returns the existing Promise.execute<T>(key: string, fn: () => Observable<T>): Observable<T>Executes an Observable-based operation. UsesshareReplayinternally to multicast the result to all subscribers.has(key: string): booleanChecks if an operation with the givenkeyis currently in-flight.
License
ISC
