Search code examples
javascripterror-handlingpromisees6-promisecatch-block

Dealing with errors in promises that are unanticipated and thus don't trigger reject()


In the following javascript code, why does the error (generated by the assignment to an non-existing object property inside the promise) not get caught by the .catch() on the promise, but does get caught by the window.onunhandledrejection function? I would like to be able to catch errors in my promises using .catch() even if they are not errors I have thought of in advance and thus haven't thought to write code to explicitly call reject().

window.onunhandledrejection = function( error ) {
    console.log(error.reason);
};

function test() {
    return new Promise( async function( resolve, reject ) {
        objectThatDoesntExist.property = 1;
    } );
}

test().catch( (error) => { console.log("Caught " + error) } );

Solution

  • Remove the async from the new Promise( async function( ...)) and it works as expected. That async is catching the exception and turning it into a rejected promise which the promise is not expecting. It is not part of the contract with the new Promise() constructor that you pass it a function that returns a promise that it should pay attention to.

    Instead, the Promise constructor catches synchronous exceptions in the executor function or waits for you to call the resolve() or reject() callbacks and pays zero attention to whatever is returned from the executor callback function.

    You can see here that if you remove the async keyword, then it works as expected and the exception is caught in the .catch().

    function test() {
        return new Promise(function( resolve, reject ) {
            objectThatDoesntExist.property = 1;
        } );
    }
    
    test().catch( (error) => { console.log("Caught " + error) } );

    Note: One might ask "why" the Promise constructor doesn't support the case when it's made async. It seems like a promise-friendly thing to do. I don't know the actual logic that went into the decision not to support that, but I do know that whenever you're using async inside a new Promise() executor function, that's typically a sign that you don't need the new Promise() wrapper at all. If you're using async for a reason, then you already have promises. If you already have promises, then you typically don't need to wrap it in new Promise() as you can use/return the promises you already have.