Or: two-color functions.
The problem: async is infectious.
There's a famous essay about this, called What Color is your Function, which I recommend reading.
For example, say you're writing a bundler. It needs to fetch resources, but is agnostic to how those resources are fetched, so it takes a readResource function from its caller. In some contexts the caller might have those resources synchronously available; in others not. That is, caller might have a synchronous readResource which returns a resource immediately, or an async readResources which returns a promise for a resource, which will need to be unwrapped with await.
You can't easily write single function which handles both cases. You either need to write two functions or, more likely, just say your function is always async, even when called in a way which would require no async behavior.
And choosing to make your function async make it unsuitable for use in any synchronous code. So that code is going to need to become async too in order to use your function, which is at best annoying and at worst impossible.
This is bad.
A new syntax for defining functions which are optionally asynchronous, depending on how they're called. Instead of await, these contain await?, which behaves exactly like await when the function is called async-style, and is a no-op when called sync-style.
In that way, you can write a single function literal which is usable in both sync and async contexts (although it would define two functions).
E.g.:
async? function bundle(entrypoint, readResource) {
let dependencies = parseForDependencies(entrypoint);
for (let dependency of dependencies) {
let result = await? readResource(dependency);
}
// etc
}
// in synchronous contexts:
let result = bundle.sync(entrypoint, fs.readFileSync); // readFileSync synchronously returns a resource
console.log(result); // not a Promise
// in async contexts:
let result = bundle.async(entrypoint, fetch); // fetch returns a promise for a resoruce
console.log(result); // a Promise
-
There should be a way for the function to switch on whether it is called as async, e.g. a
function.asyncmeta-property:let read = function.async ? fs.promises.readFile : fs.readFileSync. -
for await? (x of y)would work similarly: exactly likefor-ofwhen called assync, exactly likefor-awaitwhen called asasync. -
Assume the obvious extension to
async? function*generators. -
Result of the function definition is a
{ sync, async }pair, not itself a callable. Both properties are regular functions. -
There is no reflection involved - whether the function is
asyncdepends only on how it's called, not what values are passed to it.