Promise reject() causes "Uncaught (in promise)" warning
JavascriptPromiseEs6 PromiseCatch BlockJavascript Problem Overview
Once a promise reject()
callback is called, a warning message "Uncaught (in promise)" appears in the Chrome console. 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:
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
Javascript Solutions
Solution 1 - Javascript
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) );
Solution 2 - Javascript
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.
Solution 3 - Javascript
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.
Solution 4 - Javascript
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
Solution 5 - Javascript
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);
});
};