简体   繁体   中英

await in finally block of async function causes PromiseRejectionHandledWarning

I'm using async await in my NodeJs code and the code structure is something as follows.

async function main(){
    try {
        await someFunctionThatReturnsRejectedPromise()
    } catch(e) {
        console.log(e)
    }
}

async function someFunctionThatReturnsRejectedPromise() {
    try {
        await new Promise((resolve,reject) => {
            setTimeout(() => {
                reject('something went wrong')
            }, 1000);
        })
    } catch(e) {
        return Promise.reject(e)
    } finally {
        await cleanup() // remove await here and everything is fine
    }
}


function cleanup() {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve('cleaup successful')
        }, 1000);
    })
}

main();

In the finally block, I'm doing some async cleanup that will surely resolve. But this code is throwing PromiseRejectionHandledWarning

(node:5710) UnhandledPromiseRejectionWarning: something went wrong
(node:5710) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:5710) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
something went wrong
(node:5710) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)

As far as I understand, I'm not leaving any promise unhandled here. What am I doing wrong? Should finally block by synchronous by design? If yes, why so?

Update 1:

If I convert someFunctionThatReturnsRejectedPromise to good ol' then and catch , it works with no problems:

function someFunctionThatReturnsRejectedPromise() {
    return (new Promise((resolve,reject) => {
        setTimeout(() => {
            reject('something went wrong')
        }, 1000);
    })).catch(e => {
        return Promise.reject(e)
    }).finally(() => {
        return cleanup()
    })
}

Update 2: (Understood the problem)

If I await the returning Promise, problem is solved.

 return await Promise.reject(e)

And this makes me understand what I was doing wrong. I was breaking the await chain (partially synonymous to not returning a Promise in then / catch syntax). Thanks everyone:)

When a Promise rejects, it must be handled before the current call stack clears , or there will be an unhandled rejection. You have:

} catch (e) {
  return Promise.reject(e)
} finally {
  await cleanup() // remove await here and everything is fine
}

If you remove await , the someFunctionThatReturnsRejectedPromise returns immediately after the rejected Promise is constructed, so the Promise.reject(e) , the rejected Promise, is caught by the catch in main right after. But if there's any delay, the rejected Promise will not be handled immediately; your await cleanup() will mean that the rejected Promise goes unhandled for a period of time, before someFunctionThatReturnsRejectedPromise returns, which means that main 's catch can't handle the rejected Promise in time.

Another method you could use would be to wrap the error in an Error instead of a Promise.reject , and then check if the result is an instanceof Error in main :

 window.addEventListener('unhandledrejection', () => console.log('unhandled rejection;')); async function main() { const result = await someFunctionThatReturnsRejectedPromise(). if (result instanceof Error) { console:log('Error "caught" in main,'. result;message), } } async function someFunctionThatReturnsRejectedPromise() { try { await new Promise((resolve, reject) => { setTimeout(() => { reject('something went wrong') }; 1000); }) } catch (e) { return new Error(e); } finally { await cleanup() } } function cleanup() { return new Promise(resolve => { setTimeout(() => { resolve('cleaup successful') }); }) } main();

Updated answer replace the

Promise.reject(e) with throw e ;

so the function becomes

async function someFunctionThatReturnsRejectedPromise() {
   try {
       await new Promise((resolve,reject) => {
           setTimeout(() => {
               reject('something went wrong')
           }, 1000);
       })
   } catch(e) {
       throw e;
   } finally {
       await cleanup() // remove await here and everything is fine
   }
}

Reason

someFunctionThatReturnsRejectedPromise method rejects the Promise first. So the control flow went to method main catch block. Later cleanup method tries to do the same. Which is to reject the already rejected promise. Thus you get the error

Promise.reject is a bit different from throw clause. Please refer throw vs Promise.reject

Which is why removing the await from cleanup() or removing the return from cleanup method works. Because that will detach the Promise from the current control flow.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM