promise-cachex
v1.7.0
Published
A simple promise cache
Downloads
34
Readme
PromiseCacheX
🚀 High-Performance Promise-Based Caching for JavaScript & TypeScript
PromiseCacheX is a lightweight caching library designed to store and manage asynchronous promises and synchronous values efficiently. It eliminates redundant requests, prevents race conditions, and automatically cleans up expired cache entries.
📚 Installation
npm install promise-cachex🔧 Usage
import { PromiseCacheX } from "promise-cachex";
const cache = new PromiseCacheX({ ttl: 5000, cleanupInterval: 2000 }); // 5s TTL, cleanup every 2s
async function fetchData() {
return new Promise((resolve) =>
setTimeout(() => resolve("cached data"), 1000)
);
}
(async () => {
const result1 = await cache.get("key1", fetchData, { ttl: 5000 });
console.log(result1); // 'cached data'
const result2 = await cache.get("key1", fetchData, { ttl: 5000 });
console.log(result2); // Returns cached value immediately
})();
// Supports caching synchronous values too
cache.get("key2", "static value");
console.log(await cache.get("key2", "static value")); // 'static value'⚡ Features
✅ Promise-Aware – Stores and returns pending promises to avoid duplicate calls. ✅ Supports Both Async and Sync Values – Cache promises, async functions, sync functions, or direct values. ✅ TTL Expiry – Items automatically expire after a configurable time. ✅ Automatic Cleanup – Removes expired entries at a regular interval. ✅ Manual Deletion – Allows explicit cache clearing when needed. ✅ Error Handling – Removes failed promises from the cache. ✅ Efficient & Fast – Optimized for speed and low memory overhead.
🐜 API
constructor(options?: CacheOptions)
Creates a new instance of PromiseCacheX.
| Option | Type | Default | Description |
| ----------------- | -------- | -------------------- | ------------------------------------------------- |
| ttl | number | 3600000 (1 hour) | Default TTL in milliseconds. 0 means no TTL. |
| cleanupInterval | number | 300000 (5 minutes) | Interval in milliseconds to remove expired items. |
get<T>(key: string, fetcherOrPromise: FetchOrPromise<T>, options?: ItemOptions): Promise<T>
Retrieves a cached value or fetches and caches it if not available.
| Option | Type | Default | Description |
| ------ | -------- | --------- | ------------------------------------------ |
| ttl | number | Cache TTL | TTL for the cached item. 0 means no TTL. |
FetchOrPromise can be:
- An async function returning a promise (
() => Promise<T>) - A synchronous function returning a value (
() => T) - A direct promise (
Promise<T>) - A direct value (
T)
// Caching an async function
const result = await cache.get("key1", async () => "value", { ttl: 5000 });
// Caching a synchronous function
const syncResult = await cache.get("key2", () => "sync value");
// Caching a direct promise
const promiseResult = await cache.get(
"key3",
Promise.resolve("promised value")
);
// Caching a direct value
const directResult = await cache.get("key4", "direct value");set<T>(key: string, value: T | Promise<T>, options?: ItemOptions): void
Sets a value in the cache.
cache.set("key1", "value1", { ttl: 5000 });delete(key: string): void
Removes a specific key from the cache.
cache.delete("key1");clear(): void
Clears all cached entries.
cache.clear();size(): number
Returns the number of cached items.
console.log(cache.size());keys(): string[]
Returns an array of cached keys.
console.log(cache.keys());has(key: string): boolean
Checks if a key exists in the cache.
console.log(cache.has("key1"));🔤 Typing Modes: Strict vs Loose (Generics)
PromiseCacheX lets you choose between strict, single-type caches and a loose, multi-type cache—and still allows per-call type parameters on get/set.
How it works
The class is generic: PromiseCacheX<T = unknown>.
- If you omit
T, the cache runs in loose mode (accepts mixed value types). - If you provide
T, the cache runs in strict mode (all values must conform toT). - You can still provide a type argument to
get<U>()andset<U>(), but it is constrained so thatUmust extend the cache’s type.
Method signatures (simplified):
class PromiseCacheX<T = unknown> {
get<U extends T = T>(
key: string,
fetcherOrPromise: (() => Promise<U> | U) | Promise<U> | U,
options?: { ttl?: number }
): Promise<U>;
set<U extends T>(
key: string,
value: U | Promise<U>,
options?: { ttl?: number }
): void;
}Note: When
Tis omitted, the library treats it as “loose” so mixed types are allowed. WhenTis provided,Uis constrained to that type.
🧰 Loose mode (no generic) — store multiple types
When you don’t pass a generic, you can mix types freely. You may still annotate each call for clarity.
import { PromiseCacheX } from "promise-cachex";
const loose = new PromiseCacheX(); // T omitted → loose mode
// Store different types
await loose.get<number>("n1", 42);
await loose.get<string>("s1", () => "hello");
await loose.get<{ id: string }>("u1", Promise.resolve({ id: "abc" }));
// All OK — loose mode accepts themUse this for a shared utility cache with heterogeneous values.
🔒 Strict mode (typed cache) — enforce one value type
Provide T to restrict the cache to a single type. Per-call generics on get/set must extend that type.
type User = { id: number; name: string };
const strict = new PromiseCacheX<User>(); // typed cache
// ✅ OK: value matches `User`
await strict.get<User>("u:1", () => ({ id: 1, name: "Ana" }));
// ❌ Error: `string` does not extend `User`
// await strict.get<string>("bad", "oops");
// ✅ OK: promise of `User`
strict.set("u:2", Promise.resolve({ id: 2, name: "Ion" }));This is ideal for domain caches (e.g., Users, Products) where consistency matters.
🎯 Narrowing inside strict mode
Because U extends T, you can narrow on a call when it’s safe:
type User = { id: number; name: string };
type MaybeUser = User | null;
const cache = new PromiseCacheX<MaybeUser>();
// ✅ OK: `User` is a subtype of `User | null`
const u = await cache.get<User>("u:1", async () => ({ id: 1, name: "Ana" }));
// ✅ Also OK: storing `null`
await cache.get<MaybeUser>("u:2", null);
// ❌ Error: `string` not assignable to `User | null`
// await cache.get<string>("bad", "nope");Tip: Using unions like
User | nulllets you express cacheable absence while keeping strong typing.
✅ When to use which
- Loose mode (omit
T): quick utility cache, heterogeneous values, prototyping. - Strict mode (
PromiseCacheX<T>): domain caches with strong guarantees and easier refactors.
📊 Benchmark Results
Here are the latest performance benchmarks for PromiseCacheX:
Operations Performance
| Task | Latency Avg (ns) | Throughput Avg (ops/s) | | ------------------------------------ | ---------------- | ---------------------- | | Cache 1,000 Keys | 216,853 | 4,824 | | Cache 10,000 Keys | 2,213,626 | 461 | | Retrieve Cached Values (1,000 keys) | 221,039 | 4,756 | | Retrieve Cached Values (10,000 keys) | 2,136,370 | 476 |
Memory & CPU Usage
| Action | Memory (MB) | CPU Time (ms) | | ---------------------------- | ----------- | ------------- | | After caching 1,000 keys | 153.22 | 21,296 | | After caching 10,000 keys | 208.22 | 39,687 | | After retrieving 1,000 keys | 206.88 | 60,765 | | After retrieving 10,000 keys | 271.31 | 83,984 |
🔥 Why Use PromiseCacheX?
- 🏆 Prevents duplicate async requests (efficient shared promises)
- ⚡ Fast and lightweight (optimized caching)
- 🛡 Ensures memory efficiency (auto-expiring cache)
- 🔥 Great for API calls, database queries, and computations
- 🌟 Supports both async and sync values (no need for multiple caching libraries)
📜 License
MIT License.
🚀 Try PromiseCacheX today!
