4 ms·
This just motivated me to play around a little bit with JS async/await implementation. What I found interesting is that async functions will always return promi
by Matthias247 9y ago
This just motivated me to play around a little bit with JS async/await implementation. What I found interesting is that async functions will always return promises, even if an immediate value could be returned. Like for example in the following function:
async function getFromCacheOrRemote() {
if (random()) {
return "Got it";
} else {
await DoSomethingLongRunnning();
return "Got from network";
}
}
The function will return a Promise independent of which branch is taken, although it could return a string for the first branch. Does anybody know the reason? From a consumer point of view it does not matter if the consumer uses await, since await accepts both immediates and Promises. Is it because always returning promises is more convenient for users which use Promise combinators instead of await and less bug-prone? Or does it maybe even benefit JS runtime optimizations if the returntype is always a Promise - even though the promise in both cases might be of a different subtype?
For most applications it probably doesn't matter anyway. However returning and awaiting immediate values eliminates an allocation and eventloop iteration compared to using a Promise, which is helpful for high-performance code. This is why C# now introduced custom awaitables and things like ValueTask<T>.
- qrohlf 9y agoThis "unleashes Zalgo" and is considered a major anti-pattern when designing async APIs: http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony http://blog.izs.me/post/59142742143/designing-apis-for-async...
- ryeguy 9y agoIs it? I only do some moderate node work, so I could be missing the discussion on this, but I've never seen this brought up or talked about before. I read the blog posts, but is this really a sweeping rule in the js community or is it just the opinon of a few people? Because no one seems to be doing it.
- TheAceOfHearts 9y agoIt's a general rule that everyone follows. Things should always be sync or async, not a mix of both.
- pvg 9y agoReturning a promise is the contract of an async function, the whole mechanism is built on top of promises and is supposed to integrate with them seamlessly.
- kentor 9y agoIt would be weird for async functions to be synchronous wouldn't it? Also the consumer is not required to use await with a called async function. They can use .then on it, or pass it around to other functions that consumes promises. Or, you could pass the async function to decorators that consume promise returning functions. Using experimental decorator syntax: @debouncePromise async function() { ... } Yes it is a trade off of performance for convenience and consistency. Also... the right side of await will effectively be wrapped in a simple Promise.resolve() if it is not a promise. Proof: http://babeljs.io/repl/#?babili=false&evaluate=true&lineWrap=false&presets=env&targets=Node-8&browsers=&builtIns=false&debug=false&code=(()%20%3D%3E%20%7B%0A%20%20async%20function%20a()%20%7B%0A%20%20%20%20console.log(1)%3B%0A%20%20%20%20await%201%3B%0A%20%20%20%20console.log(2)%3B%0A%20%20%7D%0A%20%20%0A%20%20async%20function%20b()%20%7B%0A%20%20%20%20console.log(3)%3B%0A%20%20%20%20await%201%3B%0A%20%20%20%20console.log(4)%3B%0A%20%20%7D%0A%20%20%0A%20%20a()%3B%0A%20%20b()%3B%0A%7D)()%3B&experimental=false&loose=false&spec=false&playground=true http://babeljs.io/repl/#?babili=false&evaluate=true&lineWrap...
- Matthias247 9y agoWeird: 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.
- jorblumesea 9y agoAsync functions will always return a Promise, it doesn't know (or shouldn't) whether the work it's doing is synchronous. This is a common JS anti pattern and should be avoided if possible. The Promise should resolve automatically, so in some ways it's almost synchronous time.