Daniel San
Daniel San

Reputation: 2036

Why a promise reject is not catched within a try catch block but produces an Uncaught error?

I'm facing a promise rejection that escapes a try catch block. The rejection causes an Uncaught exception which is something I can not comprehend.

If I add a reject handler in the Promise.then or a Promise.catch, the rejection gets captured. But I was hopping try catch will work in this situation.

What is happening here?

class HttpResponse {
    json() {
        return Promise.reject('parse error')
    }
}

function fetch() {
        return new HttpResponse();
}


const res = fetch();

try {
    res.json().then(json => {
        alert(`json: ${json}`);
    }
    //,reason => {
    //    alert(`promise.reject ${reason}`);
    //}
    )
    //.catch(reason => {
    //    alert(`promise.catch ${reason}`);
    //})
} catch (e) {
    alert(`catch{} from try catch: ${e}`);
}

Upvotes: 3

Views: 5404

Answers (3)

Randy Casburn
Randy Casburn

Reputation: 14185

The technical reason behind this behavior is that a JavaScript promise invokes one of two callback functions for success or failure.

A promise does not emit an Error that is required for the try to work. it is not an Error (or more technically accurate) an instance of Error. It emits an event. You are encouraged to emit a new Error() if you need it to emit one. As pointed out here: https:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…

It emits an event that you can set up a handler for: https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent

Finally, await throws an Error as described in the spec: https://tc39.es/ecma262/#await-rejected

Upvotes: 2

Daniel San
Daniel San

Reputation: 2036

Technical reasons behind:

HostPromiseRejectionTracker is an implementation-defined abstract operation that allows host environments to track promise rejections.

An implementation of HostPromiseRejectionTracker must complete normally in all cases. The default implementation of HostPromiseRejectionTracker is to unconditionally return an empty normal completion.

https://www.ecma-international.org/ecma-262/10.0/index.html#sec-host-promise-rejection-tracker

Basically javascript engines can freely implement this spec. In the case of browsers you don't get the Error captured inside the try/catch because the error is not emitted where you think it should be. But instead it's tracked with this special event that throws the error in the console. Also on nodejs, this event causes the process to exit if you have node set to exit on unhandled exceptions.

On the other side, if you instead use async/await, the rejection is treated like an 'error' in practical terms. Meaning that the newer async/await feature behaves in a different fashion showing that it is not only syntactic sugar for Promises.

https://tc39.es/ecma262/#sec-throwcompletion

In sum, if you use Promise.then you are forced to provide a reject argument or chain it with .catch to have the rejection captured or else it will reach the console and in case of nodejs to exit the process if configured to do so (I believe new nodejs does this by default). But if you use the newer async/await syntax you not only have a concise code (which is secondary) but a better rejection handling because it can be properly nested in a try/catch and it will behave like an standard Error.

Upvotes: 1

Emiel Zuurbier
Emiel Zuurbier

Reputation: 20954

Promises have their own mechanism of handling errors with the catch() method. A try / catch block can't control what's happening when chaining methods.

function fetch() {
  return Promise.reject('parse error');
}

const res = fetch();
res.then(json => {
  console.log(`json: ${json}`);
}).catch((e) => {
  console.log(`catch{} from chained catch: ${e}`);
});

However, using async / await changes that. There you don't use the methods of a promise but handle errors with a try / catch block.

function fetch() {
  return Promise.reject('parse error');
}

(async function() {
  try {
    const res = await fetch();
  } catch (e) {
    console.log(`catch{} from try catch: ${e}`);
  }
})();

Upvotes: 2

Related Questions