I recently wrote about asyncPool
, one of my favorite JavaScript / TypeScript helpers, and today I want to share an even simpler yet extremely useful utility: run
!
Here's the definition:
Or, a one-liner:
Now, you're probably thinking to yourself: "That's a silly function! It just calls a function." And yes, run
is simple, but its deceptively useful too.
run
has two primary use cases:
- IIFE (Immediately Invoked Function Expression)
do
expression (currently Stage 1 Proposal)
Lets look at some examples.
1. Use as an IIFE
When acting like an IIFE, run
takes a sync or async function and...runs it! To my eyes, the version with run
is cleaner.
2. Use as a do
expression
do
expressions are a Stage 1 proposal for JavaScript that are oriented towards functional programming and composability. Here's an example:
Unfortunately, this proposal is a few years old and seems stale. Fortunately, we can get almost all of the benefit of do
expressions with run
.
When acting like a do
expression, run
executes a function and returns its value, which brings with it all the benefits of early returns via if
/else
statements without needing to extract the function out of its parent function context.
This is handy for a few reasons:
- Inline early returns
- Simple variable assignment
- Receive parent scope
- Don't need to declare and name a function
Use in Templating Languages
These do
-like expressions are especially helpful in templating languages like JSX where there is conditional logic requiring if
statements.
And, let me be clear: multi-line ternary operators are bad. Nobody wants ternary hell. Long ternaries are unreadable and hard to refactor, and I see them all the time in React projects.
Wouldn't this be nice to use in your next frontend project?
And now you might be thinking "but why not just make that another component?". And sure, you could do that, and in some cases it makes absolute sense to do that.
But, in many cases, your complex conditional rendering does not need to be abstracted into new components. Instead, you can break the pieces of rendering into logical run
functions. This removes the need to make largely useless display components just to benefit from early returns.
Advanced Use Case: Promises
When working with Promises, run
makes more sense than do
because it doesn't use the async
scope of the parent and instead allows you to create and execute async
functions inline.
Let's say we are writing a deploy script, and we are deploying two things at once: 1) a dockerized server and 2) lambda functions. In this case, run
can be used to build and deploy both at the same time while maintaining parent scope.
Remember: run
returns the result of running the function it is passed. When passed an async
function, it will return a Promise
.
Conclusion
In my most recent project, I used run
over 300 times in a variety of circumstances. I used it everywhere: bin scripts, backend code, and frontend code. I love how run
makes my code more readable and functional. And, if you use TypeScript, run
is the best for ripping apart discriminated unions and getting the correct type inference. Try it once and you'll be hooked! (run
is also useful in hooks like useMemo
but that was just a figure of speech)
I hope that this simple utility will catch on so we can stop using nested ternaries and simplify complex async operations. If you agree, spread the word by sharing this post!
FAQ
Q: "Max, will you make this into an npm
package? That would be really handy!"
A: No, its one line. That wouldn't make any sense. Ready, copy, paste!