Promise reject() causes "Uncaught (in promise)" warning
Asked Answered
D

5

74

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

Dardanus answered 25/2, 2017 at 18:35 Comment(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.Proliferate
B
68

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) );
Branch answered 25/2, 2017 at 18:40 Comment(12)
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: ecma-international.org/ecma-262/6.0/…Dardanus
You're welcome, and nice reference to the EcmaScript specs! I added this to my answer for completeness sake. Great!Branch
@Branch I have it chained and it still gives me the uncaught error messageRivard
@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.Branch
So 'then' and 'catch' should always be chained?Sapanwood
@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.Branch
@Branch 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 consoleReidreidar
@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.Branch
@Branch the reject() method returns a promise and hence the chain keeps going without breaking. codepen.io/dheeraj-br/pen/wvwxGNQ?editors=0012Reidreidar
@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.Branch
Here is an example that shows the skipping of then callbacks until the next chained catch: jsfiddle.net/7ye2ovLgBranch
I can't believe this was my issue.... So, if you have multiple unchained .then statements (or .finally) for a promise, EACH one needs an associated .catch... This answer made me realize those multiple ones should just all be chained to avoid having to define the .catch multiple times. I guess I learned something new after much suffering.Furring
S
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.

Seine answered 26/8, 2019 at 13:9 Comment(1)
"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.Branch
Q
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.

Quirita answered 26/7, 2021 at 17:15 Comment(0)
A
-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

Amesace answered 31/3, 2022 at 10:15 Comment(0)
O
-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);
    });
};
Orme answered 14/5, 2020 at 15:23 Comment(1)
This is exactly what you shouldn't do for a lazy team.Branch

© 2022 - 2024 — McMap. All rights reserved.