panzerdp / dmitripavlutin.com-comments

7 stars 0 forks source link

return-await-promise-javascript/ #135

Open utterances-bot opened 3 years ago

utterances-bot commented 3 years ago

'return await promise' vs 'return promise' in JavaScript

Is there any difference between using 'return await promise' and 'return promise' in asynchronous JavaScript functions?

https://dmitripavlutin.com/return-await-promise-javascript/

aymkin commented 3 years ago

Good to see such nitty-gritty details about promises, thx for explanation

panzerdp commented 3 years ago

Good to see such nitty-gritty details about promises, thx for explanation

You're welcome @aymkin!

WORMSS commented 3 years ago

I was actually going to demo this exact topic to my work mates. Only difference, was I would have done the try/catch in the run function.. just because that is how we roll.

lfalmeida commented 3 years ago

Cool! Thanks for clarifying these behaviors.

arryanggaputra commented 3 years ago

awesome

tomfloresa commented 3 years ago

Thanks a lot!

mvolkmann commented 3 years ago

I think this is drawing the wrong conclusion. The problem is that try/catch belongs in the run function , not in the divideWithoutAwait function. If you put it there then it will catch the rejected promise and output the desired error message.

konriz commented 3 years ago

If I would like to handle error in that return, I would do return promise.catch(err=>{}) rather than try catch block with await on return. It feels a little smelly for me to return await.

tahoemph commented 3 years ago

There is a lot to think about here. Error handling is definitely important. But the difference between return await promise and return promise isn't directly related to wrapping a try/catch around them. Think of a related implementation where your function returned a closure that did the operation. You would have the same conversation. Of course the exception is generated when you execute the closure! Here the expectation should be that you need to deal with the exception where you wait on it. More interesting is how this impacts concurrency (the aspect of using async/await that isn't demo'd computing something). Are there patterns where waiting to block on the final value later (possible pulling together multiple promises)? If I have a tree of promises and I block sub-trees early then I have the potential of building a dependency on a longer operation before I need it. Also interesting is if return promise is faster than return await promise. The second should have to wait for the promise and then wrap that return value in a new promise to return from the async function for the function that actually wants to value to await on. I suspect this is all heavily optimized in the javascript engines so maybe it isn't a big deal but it is still wasted cycles.

okhomenko commented 3 years ago

One tricky use case I've stumbled upon, when you are throwing result of the promise:

async function getData() {
  const result = await fetch(url); // Promise<{someData: any}> | Promise<{ error: string }>
  if (result.status >= 400) {
    throw await result.json();
  }
  return result.json();
}

you want json to be resolved before thowing as an error.

panzerdp commented 3 years ago

I think this is drawing the wrong conclusion. The problem is that try/catch belongs in the run function , not in the divideWithoutAwait function. If you put it there then it will catch the rejected promise and output the desired error message.

@mvolkmann Nope, the conclusion is right. Only if you want to catch the rejection of a promise you're returning, then use return await promise.

panzerdp commented 3 years ago

If I would like to handle error in that return, I would do return promise.catch(err=>{}) rather than try catch block with await on return. It feels a little smelly for me to return await.

@konriz That's a personal taste rather than a code smell. Plus, these aren't examples that argue any kind of best practice.

pietro909 commented 3 years ago

It is definitely code smell, although I agree that at this stage could be too early to say that.

I you see the divideWithAwait/divideWithoutAwait functions as part of an API and run() as the consumer of the API, catching an error and logging while returning undefined is way worse than let the error bubble up.

If you consume a promise-based API is totally legit to expect errors, that's one of the nice things about Promises that async/await is destroying for the junior devs.

panzerdp commented 3 years ago

@pietro909

It is definitely code smell, although I agree that at this stage could be too early to say that.

I think the first part of the sentence contradicts the second part of the sentence.

I you see the divideWithAwait/divideWithoutAwait functions as part of an API and run() as the consumer of the API, catching an error and logging while returning undefined is way worse than let the error bubble up.

These are just simple examples to demonstrate the difference between return promise and return await promise. These aren't examples that argue any kind of best practice.

If you consume a promise-based API is totally legit to expect errors, that's one of the nice things about Promises that async/await is destroying for the junior devs.

It depends on how you design the API. It is legit, for my example, to catch the error and return NaN:

async function divideWithAwait() {
  try {
    return await promisedDivision(5, 0);
  } catch (error) {
    return NaN;
  }
}
jbean96 commented 3 years ago

"At this step, you have seen that using return await promise and return promise don’t differ. At least when dealing with successfully fulfilled promises."

I don't believe this is quite true. return await promise explicitly returns the result of the promise which was awaited. If I have a Promise that just returns a value (i.e. const myPromise = async () => Promise.resolve(3)) then return await myPromise returns the value 3 itself. If I return myPromise that returns a Promise which resolves to 3 which needs to be handled very differently in the calling code.

I think these appeared to be the same in the code you presented above since there was an await on the return of divideWithAwait in the run function. So in the case we returned a Promise we from divideWithAwait we were awaiting the Promise in run which gave us 6 / 2 = 3. And in the case we awaited the Promise in divideWithAwait we returned 3 which we then awaited again in run which is just a no-op.

panzerdp commented 3 years ago

I think these appeared to be the same in the code you presented above since there was an await on the return of divideWithAwait

@jbean96 That's the idea: the behavior is the same if the function invocation result is awaited.

sweidac commented 1 year ago

@jbean96 You would be right if we wouldn't be inside async functions (which we need to enable the await keyword).

Async functions always return a promise. If the return value of an async function is not explicitly a promise, it will be implicitly wrapped in a promise.

from MDN

async function returnsPromise() {
    return 3; // returns Promise.resolve(3);
}

async function returnsPromiseAsWell() {
    return await Promise.resolve(3); // returns Promise.resolve(3);
}