On occasion you might need to call a function to do the same work multiple times and at the same time. For example, imagine you had some function which requests a list of data from a remote endpoint, and you had multiple dependencies on that function which would fetch that data once notified that it has changed.
Here is an example function which does "work":
const
// After 5 seconds
// doing some work
// doing some work
// doing some work
That's cool. It logs a value out once its done, repeatedly. It's illustrative. Now imagine it was resource intensive; how would running it multiple times, simultaneously, affect your infrastructure?
The good news is JavaScript treats functions as first-class citizens so we can wrap our worker function and return a new function. The new, wrapped, function would be the one we pass around to it's dependencies.
const
Maybe this code seems a bit unnecessary, however, it's doing three things:
- Returning a new function which would call the function we're wrapping;
- Caching and tracking the state of the
promise
and setting it to null once it has resolved; - Executing whichever function we passed to it and invaliding the promise.
Let's step through the code quickly by executing our worker function.
const doWorkOnce =
Wrap our earlier worker function and create a new function, doWorkOnce
. At this point, promise
is still undefined, and nothing has been executed.
// After 5 seconds
// doing some work
Now we're executing the logic in the new function. promise
would still be undefined, so the doWork
function will be called, and promise
is set to the Promise returned by doWork
. After a few seconds, the output would be logged to your console. Once the promise resolved, the state of promise
is reset to null
.
// After 5 seconds
// doing some work
After the earlier invocation, promise
is now null
, so the first call to doWorkOnce
would set it to a new promise returned by our worker function. In the second call, since promise is not null and not undefined, it assumes that we're still waiting for it to resolve, and so it returns the cached Promise. The same for the third call.
Once promise
resolves, it's once again set back to null.
A caveat
If your worker function returns different data or does different work based on the arguments you send it (which it should!), then this wrapper function would not work. For each invocation, with different arguments, we'd have the same data returned as the first call.
Here's an example:
const
I've added ...args
so that we can pass through any arguments we need to the worker function. And then I updated the worker function to print out the work its doing:
const
Running the earlier example again, we should see some weird results:
// After 5 seconds
// doing Work A
Hmmm. While the promise is cached, the arguments for the function are not. So even though we're expecting it to do something different each time, the worker function never sees the new arguments we pass it. With some creativity, you should be able to make it work. Ideally you'd want output like this:
// doing Work A
// doing Work B
// doing Work C
This solution is only useful if you're doing the exact same work for every invocation.