Briefly, the return
is different from return await
when you’re inside a block try
.
In the specific case of the question, there is no difference. There, return
is the same as return await
because you’re off a block try
(which is where the difference happens).
However, there are some cases where there may be a difference. Most of the time, it is the same thing. However, it is important to know when return
and return await
are different since the behavior of the code can be slightly changed.
What is async
?
When you mark a function as async
, she at all times will return a Promise
. In case an error is launched within this asynchronous function, the returned promise will be of the type rejected
. Otherwise, it will be solved with some value. In the case of not using a return
explicitly, a solved promise with value will be returned undefined
.
Overall, it is ideal to use asynchronous functions only when you use a await
explicit within them. Otherwise, the code may end up being counterintuitive for some who do not know exactly the behavior of asynchronous functions.
If you just want to return a promise (as in the question code), it is not necessary to use async
or await
, since there is no real need to await the completion of the promise.
The operator await
The operator await
can only be used inside functions marked as asynchronous. It basically waiting by the conclusion of the promise given to him:
- If the promise is resolved, it will "unpack" the value from within the
Promise
, which will be the value returned by the expression.
- If the promise is rejected, it will throw the error, which can be captured in blocks
try
/catch
.
From this second item of the list above, it is possible to infer the difference between the return
and the return await
.
Returning a promise - return <promise>
Consider the example below, where we have an asynchronous function foo
calling a function waitAndMaybeReject
, that returns a promise that can resolve with any value or reject with some error.
async function foo() {
try {
// ↓↓↓↓↓↓
return waitAndMaybeReject();
}
catch (e) {
return 'caught';
}
}
The function waitAndMaybeReject
returns a promise. However, foo
does not really wait for the conclusion of this promise. The function foo
simply returns (immediately) to Promise
returned by the application of waitAndMaybeReject
. In this case, there occurs a "delegation" of the promise.
As the promise was returned before being, in fact, resolved or rejected, one can affirm that the try
/catch
is useless there, because even if waitAndMaybeReject
reject itself with some error, the catch
will not be able to capture him, since the promise will already have been returned to those who called foo
.
Returning the value of a completed promise - return await <promise>
Now consider this code (still with the same function waitAndMaybeReject
of the previous example):
async function foo() {
try {
// ↓↓↓↓↓↓ ↓↓↓↓↓
return await waitAndMaybeReject();
}
catch (e) {
return 'caught';
}
}
Note now that as the operator was used await
, we are indeed awaiting the completion of the promise returned by waitAndMaybeReject
. Thus, in the case of the promise returned by waitAndMaybeReject
is rejected, the block catch
will be able to capture the exception.
The promise, with the return await
was not delegated by foo
to its calling. On the contrary: the promise was long-awaited for foo
and its value resolved (or rejected) were properly treated by foo
.
Note that now foo
returns the value solved by the promise that waitAndMaybeReject
returns, OR "caught"
, in the event of rejection of the promise in question.
Completion
Generally, return await
only makes a difference when the return
is wrapped by some block try
/catch
.
If you need to treat the value that will be completed by a promise before to be actually returned, use return await
. Otherwise, return
(delegate the promise) it makes no difference, since, as we have seen, every asynchronous function always returns a promise.
Note that there is no difference in execution time. The difference in fact is where the promise will be completed.
Personally, I don’t like to wear return await
because people don’t really understand this difference (often because they don’t even know it exists). Let’s face it, Javascript is and always has been a language with a huge number of subtle differences like this.
The following code has the same effect as return await
. The difference is that, in my view, it is clearer:
function foo() {
try {
const val = await waitAndMaybeReject();
return val;
} catch (e) {
return 'caught';
}
}
Is the same as:
function foo() {
try {
return await waitAndMaybeReject();
} catch (e) {
return 'caught';
}
}
It’s up to you to decide which one you prefer.
Like the return await
only has a differential effect when inside blocks try
/catch
, Eslint provides a rule called no-return-await
who takes care of it for you.
I don’t believe you, show me all this!
See the functional example below:
fooWithReturn(true)
.then((resolvedValue) => console.log('1 fooWithReturn(throw).then =', resolvedValue)) // <não executará>
.catch((errorValue) => console.log('1 fooWithReturn(throw).catch =', errorValue)); // Erro não tratado!
fooWithReturn(false)
.then((resolvedValue) => console.log('2 fooWithReturn(dont throw).then =', resolvedValue)) // Yay!
.catch((errorValue) => console.log('2 fooWithReturn(dont throw).catch =', errorValue)); // <não executará>
fooWithReturnAwait(true)
.then((resolvedValue) => console.log('3 fooWithReturnAwait(throw).then =', resolvedValue)) // caught
.catch((errorValue) => console.log('3 fooWithReturnAwait(throw).catch =', errorValue)); // <não executará>
fooWithReturnAwait(false)
.then((resolvedValue) => console.log('4 fooWithReturnAwait(dont throw).then =', resolvedValue)) // Yay!
.catch((errorValue) => console.log('4 fooWithReturnAwait(dont throw).catch =', errorValue)); // <não executará>
async function fooWithReturn(shouldThrow) {
try {
return waitAndMaybeReject(shouldThrow);
} catch (e) {
return 'caught';
}
}
async function fooWithReturnAwait(shouldThrow) {
try {
return await waitAndMaybeReject(shouldThrow);
} catch (e) {
return 'caught';
}
}
function waitAndMaybeReject(shouldThrow) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldThrow) {
reject('Erro não tratado!');
} else {
resolve('Yay!');
}
}, 500);
});
}
The examples of this response were based on the article "await vs Return vs Return await", by Jake Archibald.
I did some tests here, but I couldn’t verify this "waste of time" that you say exists. The code ran "at the same time" with and without the delegation. I could try to illustrate it more concretely?
– Luiz Felipe
Ah, the waste of time is very subtle. Unless it’s a really expensive call (like an HTTP call), you won’t even notice. And even if it is called HTTP, the waste of time would occur if another function could finish in the time it waited for this solution. When I say "waste of time", it’s about how the JS interpreter works. It will "waste time" solving, only then it will return. It could simply return and it would be all right. But as I said, it’s subtle and only noticeable on a large scale processing.
– Igor Cavalcanti
But the waiting time is associated with those who will in fact wait for the resolution of the promise. And I do not believe that the function that (in fact) makes this waiting has any difference. It makes no difference if function
a
wait or if functionb
(call fora
) wait. After all, once you have the value, it is not necessary to wait for it again. As the math says, the order of the factors does not change the product. If there is actually a time difference, it will be infinitesimal, but I don’t think it will be associated with the cost of the call (as in HTTP), but rather with the fact that there is an extra function in the stack.– Luiz Felipe
But the function
a
does not know that it has the value, even ifb
it’s already solved. It’s a new trial. That’s why bad practice. Unless the JS is optimized for this (I don’t really know), it’s two stroke. The difference is that the resolution time is super tiny, but it makes a hell of a difference when you have hundreds of thousands of calls solving something really expensive, like HTTP calls, database connections and so on.– Igor Cavalcanti
So, HTTP has already been surpassed, but the time to resolve Promise, return the state and return to the stack, no. If I waste a single time every time, it accumulates. And when accumulated is costly.
– Igor Cavalcanti
I thought I answered who asked the question in my first kkkkkk answer but yes, you should know everything I said and perhaps question this waste of time. But it really exists. I even did refactoring work on
returns
by perceiving an application’s misbehavior when it was clogged with requests. Perhaps the framework used was not very well designed, but we were able to receive more requests per second.– Igor Cavalcanti
A new promise will indeed be created, but at this point (without delegating the promise), the value is already known, therefore, waiting time is unique it is not possible or justifiable to wait for something that is already possessed. In addition, Javascript, being guided by asynchronous calls (increasingly tied to promises), could not allow this type of delay. That is why I insist that there is no time difference (regarding the time to wait for the conclusion of the promise).
– Luiz Felipe
Let’s go continue this discussion in chat.
– Igor Cavalcanti