The Async Singleton Pattern
In this lesson, you will learn how to guarantee that an expensive asynchronous resource is initialized exactly once, even when multiple parts of your application request it simultaneously.
The Problem of Multiple Initializations
Imagine you have a function initDatabaseConnection() that takes two seconds to run. If five different modules in your application call this function at the same time, you risk triggering five separate, expensive connections. This is inefficient and can overwhelm your database.
The Async Singleton Pattern solves this by caching the promise of the initialization rather than just the result.
How It Works
Instead of running the function immediately, we store the promise returned by the initialization. When a second caller asks for the resource, they don't trigger the function again; they simply await the same promise that is already "in flight."
let connectionPromise = null;
async function getDatabaseConnection() {
// If we haven't started yet, trigger the initialization
if (!connectionPromise) {
connectionPromise = performExpensiveConnection();
}
// Everyone awaits the same single promise
return connectionPromise;
}
Why This is Powerful
By assigning the result of performExpensiveConnection() to connectionPromise, we capture the pending state.
- First caller: Sees
connectionPromiseisnull, starts the task, and assigns the promise to the variable. - Second caller: Sees that
connectionPromisealready exists. They don't call the expensive function; they simply attach to the existing promise chain. - Efficiency: If the first call is still pending, the second caller will wait for the same resolution. If the first call has already finished, the second caller receives the result immediately.
This ensures that no matter how many times getDatabaseConnection() is called throughout the lifecycle of your application, the underlying expensive work is only executed a single time. This is a critical pattern for managing database pools, configuration loaders, or heavy API authentication tokens in a clean, non-blocking way.
Key Takeaway: By caching the promise itself instead of the result, you guarantee that an asynchronous operation is only executed once, effectively turning any function into a reliable singleton.