Reputation: 7952
What is the difference between
return await foo()
and
const t = await foo();
return t
http://eslint.org/docs/rules/no-return-await
Upvotes: 81
Views: 57140
Reputation: 29926
Update: The no-return-await
rule is deprecated since v8.46.0
. The world is healing.
Many here are suggesting omitting the await and writing return foo();
, but that is a plain wrong advice. There is nothing wrong with writing return await foo();
; on the contrary, omitting await
is a bug in your code, that I'll explain below. So turn off that no-return-await
rule, as the ESLint and SonarCloud teams also realized in the meantime, and deprecated it.
Omitting await
will break try-catch
blocks, will cause finally
blocks to run too early, and will cause some lines missing from the async stacktrace. It will also break explicit resource management
. In my opinion, skipping await
is a misguided attempt at some abstract sense of unfounded "optimization", saving 6 characters and a microtask (EDIT: ShortFuse just showed that it is actually not saving but adding a microtask in current v8) at the cost of code correctness. The rule of thumb is simple: if you have an async call, await it, even in return statements. Let us stick to the rule of thumb and always use return await
, and switch off that no-return-await
eslint rule.
And now the rant: I cannot stress enough how many days I spent explaining to developers who thought they had found a V8 bug when their catch
blocks didn't run, and how many long hours I have spent arguing over pull requests that attempted to wrap the body of async functions in try blocks but without adding those missing awaits. We had multiple production incidents because of this. My developers kept pointing to the no-return-await
rule, and when I challenged it they pointed to these answers on stack overflow with reputation in the 100K-s, saying "but the await is redundant! must not await!". I changed our ruleset, added and linked documentation, but developers tend to believe higher authority, so I had to sit down with them, running these examples together to make them believe that it's not a node bug, you just need that await. I had to do it every quarter with every new developer. It was a professional nightmare, convincing 70 senior developers that their basic assumptions were wrong all along. I cannot blame them, because they were relying on "reputable sources". So we need to fix it at the core of our industry, and I am grateful that ESLint changed course. Should we have pragmatically stuck to always return await asyncFoo();
from the beginning, we wouldn't have had any of these problems today, and I could have saved hundreds of engineering hours and more of my own sanity.
Little demo, that shows missing lines from the stacktrace, and demonstrates finally blocks running too early:
async function main() {
console.log("\nStatcktrace with await shows testStackWithAwait:");
await testStackWithAwait().catch(logStack);
console.log("\nStatcktrace without await shows missing testStackWithoutAwait:");
await testStackWithoutAwait().catch(logStack);
console.log("\nFinally runs before try block ends when await is omitted:");
await testFinallyWithoutAwait();
}
async function fnThatThrows() {
await delay(1);
throw new Error();
}
async function testStackWithoutAwait() {
return fnThatThrows(); // bad
}
async function testStackWithAwait() {
return await fnThatThrows(); // good
}
async function fnThatLogs() {
await delay(1);
console.log(' Running in try block');
}
async function testFinallyWithoutAwait() {
try {
return fnThatLogs(); // bad
} finally {
console.log(' Running in finally block');
}
}
function logStack(e) {
console.log(e.stack);
}
function delay(timeout, value) {
return new Promise(resolve => {
setTimeout(() => {
resolve(value);
}, timeout);
});
}
main().catch(console.error);
On Chrome 103 and 120 on Windows I get the following logs:
Statcktrace with await shows testStackWithAwait:
Error
at fnThatThrows (https://stacksnippets.net/js:23:9)
at async testStackWithAwait (https://stacksnippets.net/js:31:10)
at async main (https://stacksnippets.net/js:14:3)
Statcktrace without await shows missing testStackWithoutAwait:
Error
at fnThatThrows (https://stacksnippets.net/js:23:9)
at async main (https://stacksnippets.net/js:16:3)
Finally runs before try block ends when await is omitted:
Running in finally block
Running in try block
Upvotes: 18
Reputation: 10916
Using return await
does have some newly introduced benefits in v8 engine used by Node.js, Chrome and a few other browsers:
v8 introduced a --async-stack-traces
flag which as of V8 v7.3 is enabled by default (Node.js v12.0.0).
This flags provides an improved developer experience by enriching the Error stack property with async function calls stack trace.
async function foo() {
return bar();
}
async function bar() {
await Promise.resolve();
throw new Error('BEEP BEEP');
}
foo().catch(error => console.log(error.stack));
Error: BEEP BEEP
at bar (<anonymous>:7:9)
Note that by calling return bar();
foo()
function call does not appear at all in the error stack. Changing it to return await bar();
gives a much better error stack output:
async function foo() {
return await bar();
}
foo();
Error: BEEP BEEP
at bar (<anonymous>:7:9)
at async foo (<anonymous>:2:10)
This indeed does provide much better error stack tracing, hence it is HIGHLY encouraged to always await your promises.
Additionally, async/wait now outperforms hand written promises:
async/await
outperforms hand-written promise code now. The key takeaway here is that we significantly reduced the overhead of async functions — not just in V8, but across all JavaScript engines, by patching the spec. Source
Read more about these changes on the v8.dev blog: https://v8.dev/blog/fast-async#improved-developer-experience
Upvotes: 61
Reputation: 1864
The significant difference between return asyncFunc and return await Promise.resolve() is by following the second approach you can catch an error if something wrong inside async function.
function afunction() {
return asyncFun();
}
// with await
async function afunction() {
try {
return await asyncFun();
} catch(err) {
handleError(err);
// return error result;
}
}
Upvotes: 3
Reputation: 26557
Basically, because return await
is redundant.
Look at it from a slightly higher level of how you actually use an async
function:
const myFunc = async () => {
return await doSomething();
};
await myFunc();
Any async
function is already going to return a Promise
, and must be dealt with as a Promise
(either directly as a Promise
, or by also await
-ing).
If you await
inside of the function, it's redundant because the function outside will also await
it in some way, so there is no reason to not just send the Promise
along and let the outer thing deal with it.
It's not syntactically wrong or incorrect and it generally won't cause issues. It's just entirely redundant which is why the linter triggers on it.
Upvotes: 99
Reputation: 41
Oh I think is easy to understand, we put "await" when we wait for a specific value in order to continue a process, if the process is finished (look at the return), we don't need the await syntax anymore so.
return await foo(); //is redundant
return foo(); //is the correct way
Upvotes: -1
Reputation: 2784
Because you can just
async function() {
return foo();
}
The returned result of async function
always is Promise
, no matter you return the exact value or another Promise
object inside the function body
Upvotes: 24