Search code examples
javascriptnode.jses6-promisecleartimeout

UnhandledPromiseRejectionWarning after clearTimeout


If I clear the timer after it has rejected the promise - I get a warning "UnhandledPromiseRejectionWarning" in stdout regardless of whether I intercept 'catch' on the promise or not

checked on: node v10, node v12, google-chrome 76

let timer;
const promise = new Promise((resolve, reject) => {
    timer = setTimeout(() => {
        console.log('!timeout');
        reject('timeout error');
    });
}, 100);

promise.then(result => {
    console.log('SUCCESS:', result);
}).catch(error => {
    console.log('!onerror');
    console.error('ERROR:', error);
});

promise.finally(() => {
    console.log('!onfinnaly');
    clearTimeout(timer);
});

console.log('!endcode');

console output in nodejs:

!endcode
!timeout
!onfinnaly
!onerror
ERROR: timeout error
(node:12697) UnhandledPromiseRejectionWarning: timeout error
(node:12697) 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: 2)
(node:12697) [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.

console output in google-chrome:

!endcode
!timeout
!onfinnaly
!onerror
ERROR: timeout error
Promise.catch (async)
Uncaught (in promise) timeout error

Solution

  • If you call Promise#finally on a Promise that is rejected, the Promise returned by finally is also rejected:

    window.addEventListener('unhandledrejection', _ => console.error( 'Unhandled Rejection' ) );
    
    Promise.reject( )
      .finally( _ => _ );

    If you might be calling finally on a rejected Promise, you still need to catch the error to prevent an unhandled rejection:

    window.addEventListener('unhandledrejection', _ => console.error( 'Unhandled Rejection' ) );
    
    Promise.reject( 'foobar' )
      .finally( _ => _ )
      .catch( e => console.log( 'Caught: ' + e ) );

    If you chain your finally after the catch, instead of calling it directly on your promise, you would not have an unhandled rejection, because catch returns a Promise that will be fulfilled (unless a new error is thrown or a rejected Promise is returned from the catch callback).