Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Awaiting multiple promises inside an async function with try catch throws anyways

I can't understand why the following code doesn't throw:

const main = async () => {

    const Stop = (time) => new Promise((resolve) => setTimeout(resolve, time))

    try {
        const p1 = Stop(500).then(() => { throw new Error('Error ocurred') })
        const p2 = Stop(1000)

        await p1
        await p2
    } catch (err) {
        console.log('error catched')
    }
}

main()

But whenever I invert the order of p1 and p2 promises, like this:

const main = async () => {

    const Stop = (time) => new Promise((resolve) => setTimeout(resolve, time))

    try {
        const p1 = Stop(500).then(() => { throw new Error('Error ocurred') })
        const p2 = Stop(1000)

        await p2
        await p1
    } catch (err) {
        console.log('error catched')
    }
}

main()

Then an uncaught exception is thrown. I suppose that doing concurrent tasks like that without .catch functions is dangerous, but I thought the async code within a try catch would never throw.

Why isn't this the case, exactly?

like image 896
guillemus Avatar asked Oct 23 '25 10:10

guillemus


1 Answers

First things first, let's take a step back and remove the awaits all together:

const main = async () => {

    const Stop = (time) => new Promise((resolve) => setTimeout(resolve, time))

    try {
        const p1 = Stop(500).then(() => { throw new Error('Error ocurred') })
        const p2 = Stop(1000)
    } catch (err) {
        console.log('error catched')
    }
}

main()

We get an Uncaught (in promise) Error. This is expected since try/catch shouldn't handle the Promise rejection.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#Description

The await expression causes async function execution to pause until a Promise is settled (that is, fulfilled or rejected), and to resume execution of the async function after fulfillment. When resumed, the value of the await expression is that of the fulfilled Promise.

If the Promise is rejected, the await expression throws the rejected value.

So if we are expecting the try/catch to handle the Promise rejection, we need to keep in mind two things:

  1. We need to call await so that the expression will throw when the Promise is rejected (avoiding an Uncaught (in Promise) Error)
  2. We must call await before the Promise rejection happens

We can see this in action by adding our first await:

const main = async () => {

    const Stop = (time) => new Promise((resolve) => setTimeout(resolve, time))

    try {
        const p1 = Stop(500).then(() => { throw new Error('Error ocurred') })
        const p2 = Stop(1000)

        await p1
    } catch (err) {
        console.log('error catched')
    }
}

main()

Now this await will throw once the Promise rejects and we avoid the Uncaught (in Promise) Error.

But if we add await p2 before await p1, the p1 Promise rejects during the await p2 and before await p1 gets called.The try/catch doesn't work in time for us to properly handle the Promise rejection:

const main = async () => {

    const Stop = (time) => new Promise((resolve) => setTimeout(resolve, time))

    try {
        const p1 = Stop(500).then(() => { throw new Error('Error ocurred') })
        const p2 = Stop(1000)

        await p2
        await p1
    } catch (err) {
        console.log('error catched')
    }
}

main()

We can further observe this key sequence of awaits by changing the time so that await p2 resumes the execution of the function in time for the await p1 to be called so that the try/catch is waiting for await p1 to throw.

const main = async () => {

    const Stop = (time) => new Promise((resolve) => setTimeout(resolve, time))

    try {
        // Increased the time for p1 so the Promise resolves after p2 Promise resolves
        const p1 = Stop(1500).then(() => { throw new Error('Error ocurred') })
        const p2 = Stop(1000)

        await p2
        await p1
    } catch (err) {
        console.log('error catched')
    }
}

main()

I'd suggest using Promise.all:

  1. Easier to manage catching those annoying Errors
  2. Avoids pausing multiple times for each await used (not an issue with your code snippets since the p1 Promise and p2 Promise are running in 'parallel', but this is a common issue found in code)

const main = async () => {

    const Stop = (time) => new Promise((resolve) => setTimeout(resolve, time))

    try {
        const p1 = Stop(500).then(() => { throw new Error('Error ocurred') })
        const p2 = Stop(1000)

        await Promise.all([p2, p1])
    } catch (err) {
        console.log('error catched')
    }
}

main()
like image 52
Mark Thompson Avatar answered Oct 24 '25 23:10

Mark Thompson



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!