The async lazy initializer pattern in Javascript
How to run something asynchronous only once and when needed
Lazy initialization
How to run an async function at most once and not when it's not needed? This is where the async lazy initializer pattern helps.
I needed this pattern, for example, when I needed to run an external program to get its version string. This is a "heavy" operation as it requires starting a separate process so I did not want to run it every time the version string was needed. And it's an inherently async operation.
It could be a simple Promise, but here's the catch: it might never be needed and in that case it shouldn't be run. Since a Promise starts running when it's created, that is eager initialization.
Here's the code to achieve these requirements:
const getVersion = (() => {
let prom = undefined;
return () => prom = (prom || (async () => {
const programVersion = await version();
return programVersion;
})())
})();
It's a function that returns a Promise, so it's compatible with await
:
console.log(`Version is: ${await getVersion()}`);
The function is lazy as the async function won't run if nothing calls the getVersion
. It is also memoized as calling it multiple times produces
the same result without calling the external program multiple times.
How it works
It stores the result Promise in the prom
variable and returns that in subsequent invocations. Since a Promise can be used even when it's already resolved,
it can be used any number of times.
Generic version
Let's extract the common part to make a reusable function:
const lazyInit = (fn) => {
let prom = undefined;
return () => prom = (prom || fn());
}
const getVersion = lazyInit(async () => {
const programVersion = await version();
return programVersion;
});
console.log(`Version is: ${await getVersion()}`);
This gives an easy-to-use, no-dependencies solution.
Alternatives
It's a simplified version of async memoization, so any library that provides a more complete solution works here also, for example the memoizee package.