Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between `return await promise` and `return promise`

Given the code samples below, is there any difference in behavior, and, if so, what are those differences?

return await promise

async function delay1Second() {   return (await delay(1000)); } 

return promise

async function delay1Second() {   return delay(1000); } 

As I understand it, the first would have error-handling within the async function, and errors would bubble out of the async function's Promise. However, the second would require one less tick. Is this correct?

This snippet is just a common function to return a Promise for reference.

function delay(ms) {   return new Promise((resolve) => {     setTimeout(resolve, ms);   }); } 
like image 712
PitaJ Avatar asked Aug 01 '16 21:08

PitaJ


People also ask

Does return await return a Promise?

However, if you want to catch the rejected promise you're returning from an asynchronous function, then you should definitely use return await promise expression and add deliberately the await .

What is the difference between Promise and await?

1. Promise is an object representing intermediate state of operation which is guaranteed to complete its execution at some point in future. Async/Await is a syntactic sugar for promises, a wrapper making the code execute more synchronously.

What is return await?

Using return await inside an async function keeps the current function in the call stack until the Promise that is being awaited has resolved, at the cost of an extra microtask before resolving the outer Promise.

What is a Promise and a return Promise?

Promise.resolve(value) Returns a new Promise object that is resolved with the given value. If the value is a thenable (i.e. has a then method), the returned promise will "follow" that thenable, adopting its eventual state; otherwise, the returned promise will be fulfilled with the value.


2 Answers

Most of the time, there is no observable difference between return and return await. Both versions of delay1Second have the exact same observable behavior (but depending on the implementation, the return await version might use slightly more memory because an intermediate Promise object might be created).

However, as @PitaJ pointed out, there is one case where there is a difference: if the return or return await is nested in a try-catch block. Consider this example

async function rejectionWithReturnAwait () {   try {     return await Promise.reject(new Error())   } catch (e) {     return 'Saved!'   } }  async function rejectionWithReturn () {   try {     return Promise.reject(new Error())   } catch (e) {     return 'Saved!'   } } 

In the first version, the async function awaits the rejected promise before returning its result, which causes the rejection to be turned into an exception and the catch clause to be reached; the function will thus return a promise resolving to the string "Saved!".

The second version of the function, however, does return the rejected promise directly without awaiting it within the async function, which means that the catch case is not called and the caller gets the rejection instead.

like image 138
Denis Washington Avatar answered Oct 01 '22 13:10

Denis Washington


As other answers mentioned, there is likely a slight performance benefit when letting the promise bubble up by returning it directly — simply because you don’t have to await the result first and then wrap it with another promise again. However, no one has talked about tail call optimization yet.

Tail call optimization, or “proper tail calls”, is a technique that the interpreter uses to optimize the call stack. Currently, not many runtimes support it yet — even though it’s technically part of the ES6 Standard — but it’s possible support might be added in the future, so you can prepare for that by writing good code in the present.

In a nutshell, TCO (or PTC) optimizes the call stack by not opening a new frame for a function that is directly returned by another function. Instead, it reuses the same frame.

async function delay1Second() {   return delay(1000); } 

Since delay() is directly returned by delay1Second(), runtimes supporting PTC will first open a frame for delay1Second() (the outer function), but then instead of opening another frame for delay() (the inner function), it will just reuse the same frame that was opened for the outer function. This optimizes the stack because it can prevent a stack overflow (hehe) with very large recursive functions, e.g., fibonacci(5e+25). Essentially it becomes a loop, which is much faster.

PTC is only enabled when the inner function is directly returned. It’s not used when the result of the function is altered before it is returned, for example, if you had return (delay(1000) || null), or return await delay(1000).

But like I said, most runtimes and browsers don’t support PTC yet, so it probably doesn’t make a huge difference now, but it couldn’t hurt to future-proof your code.

Read more in this question: Node.js: Are there optimizations for tail calls in async functions?

like image 41
chharvey Avatar answered Oct 01 '22 12:10

chharvey