In other words, the performance price of synchronous vs asynchronous calls is the price of a function call vs Promise implementation (i.e. event-loop machinery); the price of a function call, including the stack frame allocation, which is non-zero (recall the times that assembly programmers would dismiss languages like C as 'too-slow', having to allocate on function calls), versus pushing the Promise closure onto an event-loop, exiting the current event-loop, waiting for the next event-tick, popping off the next closure from the event stack, then creating the function call stack frame under a closure.
For inner-loops, the difference can be 20ms vs 20s.
Still, the other comment about using await on plain values seems like a better option than parent comment
Awaiting plain values can only be done inside an async function, which means it returns a promise, which means you have to wait for the event loop to get the value out of there.
Generally I’m not aware of any other (reasonably ergonomic) way to write a single code path that can work with both sync and async input without itself always giving async output.
If that's okay for your code, then go for it. However, downgrading a sync function to be async has serious performance implications - turning a trivial sync function call in a tight loop into an awaited async function call will make your loop 90%+ slower [benchmark]. You're also going to allocate much more memory for Promise objects. The code I linked above is from a library implementing a sandboxed Javascript VM. If we forced all calls into the guest sandbox, or from the guest sandbox back to host functions like `console.log`, to be async, the VM would be unusable for many applications.
[MDN for await]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
[benchmark]: https://jsbench.me/y0la7auape/2
In other words, this technique allows a library's implementation and interface to be synchronity-agnostic, but it does not say anything about the library user. If the library user likewise makes use of the OP technique, the library user code will remain synchronity-agnostic, otherwise it will be tied to be either synchronous or be asynchronous (parametrised to the synchronity).