eslint: Docs: no-return-await deprecation notice leads to a wrong conclusion
Docs page(s)
https://eslint.org/docs/latest/rules/no-return-await
What documentation issue do you want to solve?
The documentation implies that removing await can lower performance and refers to an article that says for every await is created a microtask. According to the specification:
- In v8 every await leads to creation of microtask, because the engine wraps awaiting value into a IMPLICIT promise
- In v8 every async function leads to creation of microtask, because the engine wraps the async function into a IMPLICIT promise
Let’s take a look at examples
async function a() {
return fetchSomething();
}
async function b() {
return a();
}
async function c() {
return b();
}
In this example, each function (a, b, c)
is marked as async, which means their return values are automatically wrapped in a Promise
if not already a promise. Since fetchSomething()
presumably returns a promise, a
, b
, and c
essentially pass this promise up the chain. The JavaScript engine schedules promise resolutions as microtasks, but since there’s a direct pass-through of the promise without additional await
operations, the overhead is minimized to the handling of the original promise from fetchSomething()
. The key point here is that the use of async alone does not introduce unnecessary microtasks; it’s the handling of promises within these functions that matters.
async function a() {
return await fetchSomething();
}
async function b() {
return await a();
}
async function c() {
return await b();
}
Here, by introducing await
within each function, the JavaScript engine is forced to wait for the promise returned by fetchSomething()
to resolve before proceeding to the next operation, even though directly returning the promise would achieve the same effect. Each await
introduces a pause, requiring the engine to handle each resolution as a separate microtask. This means that for each await
, there’s an associated microtask for the promise resolution. The introduction of explicit await
statements in this chain creates additional points at which the engine must manage promise resolutions, potentially increasing the number of microtasks and, as a result, adding overhead. This is because each await
unnecessarily adds a layer of promise resolution that must be managed, even though the direct chaining of promises (as in the first example) would suffice.
Each async
function declaration implies that the function’s return value will be wrapped in a promise, introducing microtasks for their resolutions. However, the second example with explicit await
use in each function introduces additional microtasks due to the explicit pause and wait for each promise to resolve before proceeding.
What do you think is the correct solution?
That said, using return await inside a try/catch block is a deliberate decision that prioritizes correct error handling over potential performance concerns. In this context, the performance impact is often considered negligible compared to the benefit of ensuring errors are caught and handled properly. The choice to use return await in such scenarios should indeed be meaningful and based on the specific requirements of the code rather than following a rule blindly.
Participation
- I am willing to submit a pull request for this change.
Additional comments
No response
About this issue
- Original URL
- State: open
- Created 4 months ago
- Comments: 27 (17 by maintainers)
My understanding of the new version of the deprecation notice proposed in https://github.com/eslint/eslint/issues/18166#issuecomment-1977466594 is that the rule is deprecated because
return await
intry {}
is useful. But this rule doesn’t actually reportreturn await
intry {}
, so that’s not the reason why the rule is deprecated.As for the stack traces, the trade-off was already documented and isn’t the reason why the rule is deprecated either.
The reason this rule is deprecated is that, due to the changes in the ECMAScript specification,
return await
no longer has a negative impact on performance.