71

Once a promise reject() callback is called, a warning message "Uncaught (in promise)" appears in the Chrome console. Yet I have a catch handler in place. I can't wrap my head around the reason behind it, nor how to get rid of it.

var p = new Promise((resolve, reject) => {
  setTimeout(() => {
    var isItFulfilled = false
    isItFulfilled ? resolve('!Resolved') : reject('!Rejected')
  }, 1000)  
})

p.then(result => console.log(result))
p.catch(error => console.log(error))

Warning:

enter image description here

Edit:

I found out that if the onRejected handler is not explicitly provided to the .then(onResolved, onRejected) method, JS will automatically provide an implicit one. It looks like this: (err) => throw err. The auto generated handler will throw in its turn.

Reference:

If IsCallable(onRejected)` is false, then
     Let onRejected be "Thrower".

http://www.ecma-international.org/ecma-262/6.0/index.html#sec-performpromisethen

trincot
  • 317,000
  • 35
  • 244
  • 286
Yevgeny Kozlov
  • 1,151
  • 1
  • 8
  • 11
  • 1
    Seems you're using Angular's `ZoneAwarePromise`. They implemented the Rejection Handling incorrectly, at least in specific versions. **Remember though, *anything* returned from a Fulfillment Handler (then arguments[0]) will change the method-chaining outcome such that the next `then` invocation is coming from a new Promise with the return value as the [potential] state**. – Cody Feb 12 '19 at 23:56

5 Answers5

67

This happens because you do not attach a catch handler to the promise returned by the first then method, which therefore is without handler for when the promise rejects. You do have one for the promise p in the last line, but not for the chained promise, returned by the then method, in the line before it.

As you correctly added in comments below, when a catch handler is not provided (or it's not a function), the default one will throw the error. Within a promise chain this error can be caught down the line with a catch method callback, but if none is there, the JavaScript engine will deal with the error like with any other uncaught error, and apply the default handler in such circumstances, which results in the output you see in the console.

To avoid this, chain the .catch method to the promise returned by the first then, like this:

p.then( result =>  console.log('Fulfilled'))
 .catch( error =>  console.log(error) );
trincot
  • 317,000
  • 35
  • 244
  • 286
  • 5
    Thank you trincot. I did some extra research and found out that if onRejected handler is not explicitly passed JS will provide an implicit one: `If IsCallable(onRejected)` is **false**, then Let `onRejected` be "**Thrower**". That looks something like this: `arg => throw arg` An empty onRejected handler `() => {}` can override this behavior. But `null` won't. _ref: http://www.ecma-international.org/ecma-262/6.0/index.html#sec-performpromisethen_ – Yevgeny Kozlov Feb 25 '17 at 19:08
  • You're welcome, and nice reference to the EcmaScript specs! I added this to my answer for completeness sake. Great! – trincot Feb 25 '17 at 19:24
  • 2
    @trincot I have it chained and it still gives me the uncaught error message – Amrit Kahlon Oct 28 '17 at 00:18
  • @AmritKahlon, then you probably have something else going on. If you have researched the issue (with debugging, looking for similar questions, ...), then post a new question about it. – trincot Oct 28 '17 at 01:26
  • So 'then' and 'catch' should always be chained? – GarfieldKlon Feb 08 '19 at 13:48
  • 1
    @GarfieldKlon, they should be chained if you not only want to catch a rejection of the original promise `p`, but also want to catch an exception in the `then` callback (which rejects the promise returned by `then()`). In general, when you do `p.then(...).then(...).then(...).then(...).catch(...)` you will catch rejections anywhere in the chain of promises. – trincot Feb 08 '19 at 13:53
  • @trincot chaining of then with one catch at the end as you have mentioned in the comments will only catch _one_ exception if multiple then blocks throw exceptions only the first one will be caught and rest will be not be caught and we'll have an error message in the console – some_groceries Sep 14 '19 at 16:52
  • @Dheeraj, if an error occurs in a `then` callback, the subsequent `then` callbacks never get executed, so how could there be an exception there? When the promise returned by a `then` method is rejected, the chained `then`-callback is not called, but the promise returned by *that* `then` method is just rejected as well, and so a rejection propagates through the chain, until there is a `catch` callback to execute. – trincot Sep 14 '19 at 16:59
  • @trincot the reject() method returns a promise and hence the chain keeps going without breaking. https://codepen.io/dheeraj-br/pen/wvwxGNQ?editors=0012 – some_groceries Sep 14 '19 at 17:29
  • @Dheerja, that code is just mixed up. You create all promises even before you start the chain. That is not what chaining is about. When we talk about chaining promises, it means that promises are not created at the start, but only when the previous one resolves. You also seem to think that `return promise`, somehow *executes* something, but you just return an object, which you already created earlier. Whether you return that or not does not determine whether the `setTimeout` callback will be called. Of course it will anyhow. – trincot Sep 14 '19 at 18:45
  • Here is an example that shows the skipping of `then` callbacks until the next chained `catch`: https://jsfiddle.net/7ye2ovLg/ – trincot Sep 14 '19 at 18:52
3

Even if you use Promises correctly: p.then(p1).catch(p2) you can still get an uncaught exception if your p2 function eventually throws an exception which you intend to catch using a mechanism like window.onerror. The reason is that the stack has already been unwound by the error handling done in the promise. To fix this, make sure that your error code (called by the reject function) does not throw an exception. It should simply return.

It would be nice if the error handling code could detect that the stack has already been unwound (so your error call doesn't have to have a flag for this case), and if anyone knows how to do this easily I will edit this answer to include that explanation.

David Spector
  • 1,520
  • 15
  • 21
  • *"if the error handling code could detect that the stack has already been unwound"*: that is always the case with a `then` callback: it always runs asynchronously. – trincot Dec 07 '20 at 16:54
3

This code does not cause the "uncaught in promise" exception:

// Called from top level code; 
// implicitly returns a Promise
testRejectCatch = async function() {

    // Nested within testRejectCatch;
    // simply rejects immediately
    let testReject = function() {
        return new Promise(function(resolve, reject) {
            reject('test the reject');
        )};
    }
 
//***********************************************
// testRejectCatch entry.
//***********************************************
try {
    await testReject(); // implicitly throws reject exception
catch(error) {
    // somecode 
 }

//***********************************************
// top level code
//***********************************************
try{
    testRejectCatch()   // Promise implicitly returned,
    .catch((error) => { // so we can catch
        window.alert('Report error: ' + error);
       // must not throw error;
    });
}
catch(error) {
    // some code
}

Explanation: First, there's a terminology problem. The term "catch" is used in two ways: in the try-catches, and in the Promises. So, it's easy to get confused about a "throw"; is it throwing to a try's catch or to a Promise's catch?

Answer: the reject in testReject is throwing to the Promise's implicit catch, at await testReject; and then throwing on to the .catch at testRejectCatch().

In this context, try-catch is irrelevant and ignored; the throws have nothing to do with them.

The .catch at testRejectCatch satisfies the requirement that the original throw must be caught somewhere, so you do not suffer the "uncaught in Promise..." exception.

The takeaway: throws from Promises are throws to .catch, not to try-catch; and must be dealt-with in some .catch

Edit: In the above code, the reject propagates up through the .catches. If you want, you can convert over to propagating up the try-catches. At line 17, change the code to:

let bad = '';
await testReject().catch((error) => {bad = error});
if (bad) throw bad;

Now, you've switched over to the try-catch.

BinCodinLong
  • 721
  • 1
  • 7
  • 21
-2

I ran into this issue, but without setTimeout().

In case anyone else runs into this: if in the Promise constructor you just call reject() synchronously, then it doesn't matter how many .then() and .catch() handlers you add to the returned Promise, they won't prevent an uncaught promise rejection, because the promise rejection would happen before you

Attila Szeremi
  • 5,235
  • 5
  • 40
  • 64
-4

I've solved that problem in my project, it's a large enterprise one. My team is too lazy to write empty catch hundreds of times.

Promise.prototype.then = function (onFulfilled, onRejected) {
    return baseThen.call(this, (x: any) => {
        if (onFulfilled)
            onFulfilled(x);
    }, (x: any) => {
        if (onRejected)
            onRejected(x);
    });
};
stbear
  • 88
  • 5