Weird: Depends. I think that an async function might sometimes complete synchronously is not weird - it can happen in many scenarios where the least common denominator is "The function might sometimes take a long time and therfore has to be async". Having a different return type depending on the taken path (Value or Promise<Value>) is definitely weird and should not be encouraged (although supported by JS). I'm therefore in favor of the decision that an async function always returns a Promise - I just wanted to know if anybody has more insight on how the decision was taken and if the performance impact has been considered.
Thanks for bringing up the issue around that an await on a value will wrap it into a promise before! That means even the following "workaround" would not work:
function getFromCacheOrRemote() {
if (random()) {
return "Got it";
} else {
return DoSomethingLongRunnningWhichMightUseAsyncAwaitInternally()
.then(() => "Got from network");
}
}
var result = await getFromCacheOrRemote();
Here getFromCacheOrRemote interferes correctly as type string | Promise<string> in typescript. However if an await on the function will still trigger a Promise creation and the await an eventloop iteration it won't buy anything compared to the simply solution. Seems like to profit from synchronous completions there's also some steps on the callsite needed like:
var maybePromise = getFromCacheOrRemote();
var result;
if (typeof maybePromise === 'object' && maybePromise.then != null)
result = await maybePromise;
else
result = maybePromise;
And just for clarification: I wouldn't encourage any normal application to do this kind of things, the normal async functions should be great for them. However for some libraries (e.g. high-performance networking libraries) these optimizations can make sense. And e.g. the awaitable ValueTask<T> in C# was created with exactly those scenarios in mind.