npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

request-coalescer

v1.0.1

Published

A production-quality TypeScript library to merge duplicate concurrent asynchronous requests into a single execution by sharing the same Promise.

Readme

request-coalescer

npm version License: MIT TypeScript

A production-quality, lightweight, and type-safe TypeScript utility to merge duplicate concurrent asynchronous requests into a single execution by sharing the same Promise.

Designed to prevent Cache Stampede (Thundering Herd) problems and reduce unnecessary API and database load.


Table of Contents


The Problem

When 1,000 users concurrently request the same resource (e.g. at flash sale launch or cache expiration), your server normally fires 1,000 duplicate queries to your database or downstream API. This can crash databases, hit rate limits, and consume excessive CPU/memory.

[No Coalescing]
User 1  -----\
User 2  ------\
...            =====> [Server] =====> DB Query 1 (Slow)
User 1000 ----/                       DB Query 2 (Slow)
                                       ...
                                       DB Query 1000 (Slow)

How It Works

With request-coalescer, only the first request initiates the async work. All subsequent concurrent callers for the same key share the same pending Promise reference.

[With Coalescing]
User 1  -----> (Starts Query & Caches Promise) -------> [Database (1 call)]
User 2  -----> (Gets same Promise from Cache)
...
User 1000 ----> (Gets same Promise from Cache)

Once resolved or rejected, the Promise is immediately cleaned up from the cache, delivering the result to all callers from a single execution.


Features

| Feature | Description | |---|---| | 🎯 Core Deduplication | Shares the underlying promise reference across concurrent calls for the same key. | | 🛡️ Caller Isolation | Timeout and AbortSignal support are applied per-caller without interrupting other concurrent callers. | | ⏱️ Cache TTL | Keep successful results cached in memory for a specified duration before eviction. | | 🚦 Concurrency Limiting | Global execution limit (maxPending) prevents out-of-memory errors and client abuse. | | ⚙️ Higher-Order Wrapper | Simple .wrap() helper to adapt existing async functions with zero boilerplate. | | 📈 Telemetry | Built-in hooks (onHit, onMiss, onResolve, onReject) and live stats tracking. | | 📦 Dual Package | Ships as ESM & CommonJS with full TypeScript declaration files. |


Installation

npm install request-coalescer
yarn add request-coalescer
pnpm add request-coalescer

Quick Start

import { RequestCoalescer } from 'request-coalescer';

const coalescer = new RequestCoalescer();

// A slow asynchronous task (e.g. database lookup or HTTP fetch)
async function fetchUserProfile(userId: string) {
  console.log(`Executing real DB query for user ${userId}...`);
  return { id: userId, name: 'Alice' };
}

// Wrap your async calls with coalescer.coalesce()
async function getUser(userId: string) {
  return coalescer.coalesce(`user:${userId}`, () => fetchUserProfile(userId));
}

// Simulating 3 concurrent calls for the same user
const [u1, u2, u3] = await Promise.all([
  getUser('user-1'),
  getUser('user-1'),
  getUser('user-1'),
]);

// Console Output:
// "Executing real DB query for user user-1..." (logged only ONCE)

Advanced Usage

1. Functional Wrapper Decorator (wrap)

Wrap any async function to automatically resolve cache keys from its arguments:

const getCoalescedUser = coalescer.wrap(
  fetchUserProfile,
  (userId) => `user:${userId}`,
  { timeout: 5000 }
);

// Call it just like the original function:
const user = await getCoalescedUser('user-1');

2. Timeouts & AbortSignal (Isolated Execution)

Each caller can control its own wait independently — this never affects the shared underlying request:

const controller = new AbortController();

const request1 = coalescer.coalesce('fetch-data', fetchData, {
  timeout: 1000, // Fails early for caller 1 if the task takes >1s
});

const request2 = coalescer.coalesce('fetch-data', fetchData, {
  signal: controller.signal, // Caller 2 can abort independently
});

// Abort caller 2 after 500ms — the underlying fetchData() call keeps running for request1
setTimeout(() => controller.abort(), 500);

3. Cache TTL (Temporary Result Cache)

Configure a Time-to-Live to keep successful values cached in memory for a short duration after they resolve:

const value = await coalescer.coalesce('key', fetchSlowData, {
  ttl: 5000, // Keeps the resolved result cached for 5 seconds. Rejections are cleared instantly.
});

4. Concurrency Limiting

Cap how many distinct requests can be active at once — useful for protecting downstream systems from overload:

const coalescer = new RequestCoalescer({ maxPending: 50 });

// The 51st concurrent unique key throws:
// Error: Max pending requests limit (50) reached

5. Telemetry Hooks

Track cache efficiency and request outcomes in real time:

const coalescer = new RequestCoalescer({
  onHit: (key) => console.log(`Cache hit: ${key}`),
  onMiss: (key) => console.log(`Cache miss: ${key}`),
  onResolve: (key, durationMs) => console.log(`${key} resolved in ${durationMs}ms`),
  onReject: (key, error, durationMs) => console.error(`${key} failed after ${durationMs}ms`, error),
});

// ...later
console.log(coalescer.getStats());
// { hits: 12, misses: 3, resolves: 3, rejects: 0, active: 0 }

API Reference

new RequestCoalescer(options?: CoalescerOptions)

Creates a new instance with default configuration applied to every call unless overridden.

interface CoalescerOptions {
  timeout?: number;      // Default timeout in ms for all requests
  ttl?: number;          // Default cache time-to-live in ms
  maxPending?: number;   // Maximum concurrent executions allowed
  onHit?: (key: string) => void;
  onMiss?: (key: string) => void;
  onResolve?: (key: string, durationMs: number) => void;
  onReject?: (key: string, error: any, durationMs: number) => void;
}

Methods

coalesce<T>(key: string, fn: () => Promise<T>, options?: CoalesceOptions): Promise<T>

Merges duplicate concurrent requests sharing the same key.

| Param | Description | |---|---| | key | A unique string identifying the request. | | fn | The underlying async operation to execute on a cache miss. | | options | Per-call overrides: timeout, ttl, signal. |

wrap<Args, R>(fn, keyGenerator, options?): (...args: Args) => Promise<R>

Returns a coalescing-aware version of fn, deriving the cache key from its arguments via keyGenerator.

getStats(): CoalescerStats

Returns a snapshot of current counters: { hits, misses, resolves, rejects, active }.

resetStats(): void

Resets all counters back to zero.

clearCache(): void

Evicts all cached and pending entries immediately. Does not cancel underlying in-flight requests.


License

MIT