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
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) );
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.
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.
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
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);
});
};
Related
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}`);
}
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}`);
}
})();
The technical reason behind this behavior is because 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 and 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 and Error as described in the spec: https://tc39.es/ecma262/#await-rejected
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.
This question already has answers here:
why promise can not catch the error throw by setTimeout? [duplicate]
(2 answers)
Closed last year.
This question concerns some nodejs code. I am trying to clear my mind on how exceptions are handled, and how promise/async functions are alike/unlike.
I have greatly simplified my code, providing a (non-functional) sample below. I have a function (uploadFile) returning a promise. That function calls another async function which throws an exception.
const Linebyline = require('line-by-line');
const uploadFile = (filename) => new Promise((resolve, reject) => {
console.log(`Upload ${filename}`);
const lr = new Linebyline(filename);
//
// Point A: throw from here works, no unhandled rejection
//
lr.on('line', async (line) => {
// Another async function throws an error here,
// replacing it with a simple "throw" to clarify things:
throw new Error("failed");
// Point B: this throw fails, unhandled rejection
}
// Other code continues, calling resolve and reject...
}
async function top() {
try {
await uploadFile('test.txt');
} catch (err) {
console.log(`Err: ${err}`);
}
}
With this scheme, I get an unhandled rejection. Since I have a try/catch in the upper level function calling uploadFile, I wax expecting that catch to handle pretty much everything going wring in sub calls.
If I catch the "throw" inside uploadFile and transform it into a reject, then all is fine.
Obviously, not calling reject, I am looking for trouble, but I don't really understand the chain of events generated by that "throw"?
Thank you.
Edit, 01/31/2019
Edited the source code, to outline the core issue.
When my function throws from point B (inside the callback), I end up with an unhandled rejection.
When throwing from A, no unhandled rejection.
The core of the question is really what happens to that exception thrown from point B.
This code sample doesn't call resolve/reject, but this is done elsewhere in the real code.
You're using Promise constructors wrong. You should be calling reject on error or resolve on success. You're doing neither. If you just call reject in your promise, then await will throw the error, which is what you're trying to make happen here.
The following two functions behave identically when called.
function func1() {
return asyncFunc()
.then(() => {
// do something
});
}
function func2() {
return asyncFunc()
.then(() => {
// do something
}).catch((err) => {
throw err;
});
}
I know that .catch() is merely syntactic sugar for Promise.prototype.then(undefined, onRejected) per MDN docs. However, I'm confused as to what actually occurs behind the scenes when you omit .catch from a promise chain.
What is actually going on behind the scenes when there is no .catch() in a promise chain? Is a .catch((err) => { throw err;}); being "magically" appended somehow?
The .catch((err) => { throw err;}) would not do anything, it would just re throw the error. So the Promise returned by .catch will be rejected with the error err again:
Promise.reject(new Error('test'))
.catch(err => {
console.error(err)
throw err
})
.catch(err => {
console.error(err)
})
func1 would return a Promise that might be rejected by some event that happens in that chain. So the caller of the func1 might want to handle that error, or if the the caller passes the received Promise further, and does not want to handle that error, then also the caller can omit the catch.
But the one "owning" the chain (the one who received it last and does not pass it to anyone else), is responsible to handle the rejection case.
function func1() {
return asyncFunc()
.then(() => {
// do something
});
}
function callerA() {
return func1()
}
function callerB() {
// callerB does not return the promise retuirned by callerA,
// and does not pass it to any other function, so it has to handle
// the rjection case
callerA().catch(err => {
})
}
On a rejection the js environment will check if there this error is catched anywhere in the chain. If that is not the case then a UnhandledPromiseRejectionWarning might be thrown:
UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
Or in case of the browser the error is logged in the console.
How the js enviroment deals with unhandled rejections depends, nodejs currently emits this warning:
DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
And as it says will terminate the application in future versions.
For the browser it is unlikely that it will terminate the tab future. It most likely will always only log that error in the console, but you still should write your code as if the unhandled rejection would terminate the context.
The two examples are definitely functionally equivalent, there is no magic happening behind the scenes. The Promise handler callbacks are designed to automatically handle values that are thrown and apply them to the promise chain.
Consider the following example:
Promise.resolve("{") // broken JSON
.then((json) => {
return JSON.parse(json) // Throws SyntaxError
})
.catch((err) => {
throw err; // Throws the same SyntaxError
})
.catch((err) => {
console.error(err); // Logs the SyntaxError
});
Most Promise implementations have a feature that logs the error when you have not attached an error handler to a promise that was rejected, some even allow you to register a custom event handler (e.g. Chrome and NodeJS) when an unhandled rejection occurs. But not all runtimes are created equal, the Promise implementation in Edge, for instance, does not provide such feature for notifying users of unhandled rejections.
Q: Is a .catch((err) => { throw err;}); being "magically" appended somehow?
A: NO, NOT AT ALL!
The .then method itself returns a promise. And, if you call a .catch method on that returned promise the handler passed in the catch would get called if that promise got rejected. The handler would simply get ignored if the promise got resolved. Read more about error handling in JS promises here.
And, as I already mentioned, the catch method doesn't magically get appended. Your code would just get ignore if the promise returned by .then method would get rejected because you didn't catch it. Run the following code for a better understanding:
var myPromise = new Promise(function(resolve, reject) {
setTimeout(reject, 100);
});
myPromise
.then(() => console.log('hi')); // Prints nothing
myPromise
.then(() => console.log('hi'))
.catch(() => console.log('hello')); // Prints hello
I am have a problem understanding why rejections are not passed on through a promise chain and I am hoping someone will be able to help me understand why. To me, attaching functionality to a chain of promises implies an intent that I am depending on an original promise to be fulfilled. It's hard to explain, so let me show a code example of my problem first. (Note: this example is using Node and the deferred node module. I tested this with Dojo 1.8.3 and had the same results)
var d = require("deferred");
var d1 = d();
var promise1 = d1.promise.then(
function(wins) { console.log('promise1 resolved'); return wins;},
function(err) { console.log('promise1 rejected'); return err;});
var promise2 = promise1.then(
function(wins) { console.log('promise2 resolved'); return wins;},
function(err) { console.log('promise2 rejected'); return err;});
var promise3 = promise2.then(
function(wins) { console.log('promise3 resolved'); return wins;},
function(err) { console.log('promise3 rejected'); return err;});
d1.reject(new Error());
The results of running this operation is this output:
promise1 rejected
promise2 resolved
promise3 resolved
Okay, to me, this result doesn't make sense. By attaching to this promise chain, each then is implying the intent that it will be dependant upon the successful resolution of d1 and a result being passed down the chain. If the promise in promise1 doesn't receive the wins value, but instead gets an err value in its error handler, how is it possible for the next promise in the chain to have its success function called? There is no way it can pass on a meaningful value to the next promise because it didn't get a value itself.
A different way I can describe what I'm thinking is: There are three people, John, Ginger, and Bob. John owns a widget shop. Ginger comes into his shop and requests a bag of widgets of assorted colours. He doesn't have them in stock, so he sends in a request to his distributor to get them shipped to him. In the mean time, he gives Ginger a rain check stating he owes her the bag of widgets. Bob finds out Ginger is getting the widgets and requests that he get the blue widget when she's done with them. She agrees and gives him a note stating she will. Now, John's distributor can't find any widgets in their supply and the manufacturer doesn't make them any more, so they inform John, who in turn informs Ginger she can't get the widgets. How is Bob able to get a blue widget from Ginger when didn't get any herself?
A third more realistic perspective I have on this issue is this. Say I have two values I want updated to a database. One is dependant on the id of the other, but I can't get the id until I have already inserted it into a database and obtained the result. On top of that, the first insert is dependant on a query from the database. The database calls return promises that I use to chain the two calls into a sequence.
var promise = db.query({parent_id: value});
promise.then(function(query_result) {
var first_value = {
parent_id: query_result[0].parent_id
}
var promise = db.put(first_value);
promise.then(function(first_value_result) {
var second_value = {
reference_to_first_value_id: first_value_result.id
}
var promise = db.put(second_value);
promise.then(function(second_value_result) {
values_successfully_entered();
}, function(err) { return err });
}, function(err) { return err });
}, function(err) { return err });
Now, in this situation, if the db.query failed, it would call the err function of the first then. But then it would call the success function of the next promise. While that promise is expecting the results of the first value, it would instead get the error message from its error handler function.
So, my question is, why would I have an error handing function if I have to test for errors in my success function?
Sorry for the length of this. I just didn't know how to explain it another way.
UPDATE and correction
(Note: I removed a response I had once made to some comments. So if anyone commented on my response, their comments might seem out of context now that I removed it. Sorry for this, I am trying to keep this as short as possible.)
Thank you everybody who replied. I would like to first apologize to everybody for writing out my question so poorly, especially my pseudo code. I was a little too aggressive in trying to keep it short.
Thanks to Bergi's response, I think I found the error in my logic. I think I might have overlooked another issue that was causing the problem I was having. This is possibly causing the promise chain work differently than I thought it should. I am still testing different elements of my code, so I can't even form a proper question to see what I'm doing wrong yet. I did want to update you all though and thank you for your help.
To me, this result doesn't make sense. By attaching to this promise chain, each then is implying the intent that it will be dependant upon the successful resolution of d1 and a result being passed down the chain
No. What you are describing is not a chain, but just attaching all the callbacks to d1. Yet, if you want to chain something with then, the result for promise2 is dependent on the resolution of promise1 and how the then callbacks handled it.
The docs state:
Returns a new promise for the result of the callback(s).
The .then method is usually looked upon in terms of the Promises/A specification (or the even stricter Promsises/A+ one). That means the callbacks shell return promises which will be assimilated to become the resolution of promise2, and if there is no success/error handler the respective result will in case be passed directly to promise2 - so you can simply omit the handler to propagate the error.
Yet, if the error is handled, the resulting promise2 is seen as fixed and will be fulfilled with that value. If you don't want that, you would have to re-throw the error, just like in a try-catch clause. Alternatively you can return a (to-be-)rejected promise from the handler. Not sure what Dojo way to reject is, but:
var d1 = d();
var promise1 = d1.promise.then(
function(wins) { console.log('promise1 resolved'); return wins;},
function(err) { console.log('promise1 rejected'); throw err;});
var promise2 = promise1.then(
function(wins) { console.log('promise2 resolved'); return wins;},
function(err) { console.log('promise2 rejected'); throw err;});
var promise3 = promise2.then(
function(wins) { console.log('promise3 resolved'); return wins;},
function(err) { console.log('promise3 rejected'); throw err;});
d1.reject(new Error());
How is Bob able to get a blue widget from Ginger when didn't get any herself?
He should not be able. If there are no error handlers, he will just perceive the message (((from the distributor) from John) from Ginger) that there are no widgets left. Yet, if Ginger sets up an error handler for that case, she still might fulfill her promise to give Bob a widget by giving him a green one from her own shack if there are no blue ones left at John or his distributor.
To translate your error callbacks into the metapher, return err from the handler would just be like saying "if there are no widgets left, just give him the note that there are no ones left - it's as good as the desired widget".
In the database situation, if the db.query failed, it would call the err function of the first then
…which would mean that the error is handled there. If you don't do that, just omit the error callback. Btw, your success callbacks don't return the promises they are creating, so they seem to be quite useless. Correct would be:
var promise = db.query({parent_id: value});
promise.then(function(query_result) {
var first_value = {
parent_id: query_result[0].parent_id
}
var promise = db.put(first_value);
return promise.then(function(first_value_result) {
var second_value = {
reference_to_first_value_id: first_value_result.id
}
var promise = db.put(second_value);
return promise.then(function(second_value_result) {
return values_successfully_entered();
});
});
});
or, since you don't need the closures to access result values from previous callbacks, even:
db.query({parent_id: value}).then(function(query_result) {
return db.put({
parent_id: query_result[0].parent_id
});
}).then(function(first_value_result) {
return db.put({
reference_to_first_value_id: first_value_result.id
});
}.then(values_successfully_entered);
#Jordan firstly as commenters noted, when using deferred lib, your first example definitely produces result you expect:
promise1 rejected
promise2 rejected
promise3 rejected
Secondly, even if it would produce output you suggest, it wouldn't affect execution flow of your second snippet, which is a bit different, more like:
promise.then(function(first_value) {
console.log('promise1 resolved');
var promise = db.put(first_value);
promise.then(function (second_value) {
console.log('promise2 resolved');
var promise = db.put(second_value);
promise.then(
function (wins) { console.log('promise3 resolved'); },
function (err) { console.log('promise3 rejected'); return err; });
}, function (err) { console.log('promise2 rejected'); return err;});
}, function (err) { console.log('promise1 rejected'); return err});
and that, in case of first promise being rejected will just output:
promise1 rejected
However (getting to the most interesting part) even though deferred library definitely returns 3 x rejected, most of other promise libraries will return 1 x rejected, 2 x resolved (that leads to assumption you got those results by using some other promise library instead).
What's additionally confusing, those other libraries are more correct with their behavior. Let me explain.
In a sync world counterpart of "promise rejection" is throw. So semantically, async deferred.reject(new Error()) in sync equals to throw new Error().
In your example you're not throwing errors in your sync callbacks, you just returning them, therefore you switch to success flow, with an error being a success value. To make sure rejection is passed further, you need to re-throw your errors:
function (err) { console.log('promise1 rejected'); throw err; });
So now question is, why do deferred library took returned error as rejection?
Reason for that, is that rejection in deferred works a bit different. In deferred lib the rule is: promise is rejected when it's resolved with an instance of error, so even if you do deferred.resolve(new Error()) it will act as deferred.reject(new Error()), and if you try to do deferred.reject(notAnError) it will throw an exception saying, that promise can be rejected only with instance of error. That makes clear why error returned from then callback rejects the promise.
There is some valid reasoning behind deferred logic, but still it's not on par with how throw works in JavaScript, and due to that this behavior is scheduled for change with version v0.7 of deferred.
Short summary:
To avoid confusion and unexpected results just follow the good practice rules:
Always reject your promises with an error instances (follow rules of sync world, where throwing value that's not an error is considered a bad practice).
Reject from sync callbacks by throwing errors (returning them doesn't guarantee rejection).
Obeying to above, you'll get both consistent and expected results in both deferred and other popular promise libraries.
Use can wrap the errors at each level of the Promise. I chained the errors in TraceError:
class TraceError extends Error {
constructor(message, ...causes) {
super(message);
const stack = Object.getOwnPropertyDescriptor(this, 'stack');
Object.defineProperty(this, 'stack', {
get: () => {
const stacktrace = stack.get.call(this);
let causeStacktrace = '';
for (const cause of causes) {
if (cause.sourceStack) { // trigger lookup
causeStacktrace += `\n${cause.sourceStack}`;
} else if (cause instanceof Error) {
causeStacktrace += `\n${cause.stack}`;
} else {
try {
const json = JSON.stringify(cause, null, 2);
causeStacktrace += `\n${json.split('\n').join('\n ')}`;
} catch (e) {
causeStacktrace += `\n${cause}`;
// ignore
}
}
}
causeStacktrace = causeStacktrace.split('\n').join('\n ');
return stacktrace + causeStacktrace;
}
});
// access first error
Object.defineProperty(this, 'cause', {value: () => causes[0], enumerable: false, writable: false});
// untested; access cause stack with error.causes()
Object.defineProperty(this, 'causes', {value: () => causes, enumerable: false, writable: false});
}
}
Usage
throw new TraceError('Could not set status', srcError, ...otherErrors);
Output
Functions
TraceError#cause - first error
TraceError#causes - list of chained errors
a simple explanation from here:
In a regular try..catch we can analyze the error and maybe rethrow it if it can’t be handled. The same thing is possible for promises.
If we throw inside .catch, then the control goes to the next closest error handler. But if we handle the error and finish normally, then it continues to the next closest successful .then handler.
In the example below the .catch successfully handles the error:
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(function(error) {
alert("The error is handled, continue normally");
}).then(() => alert("Next successful handler runs"));
Here the catch block finishes normally. So the next successful then handler is called.
note that we may have as many .then handlers as we want, and then use a single .catch at the end to handle errors in all of them.
If you have mid catch blocks and you want to break the next chain functions for errors, you shall re-throw the errors inside the catch blocks to signal this error is not handled completely.
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(function(error) { // (*) first catch
if (error instanceof URIError) { //just as example
// handle it...
} else {
alert("Can't handle such error");
throw error; // throwing this jumps to the next catch
}
}).then(function() {
// our error is other than URIError, so:
// the code doesn't reach here (jump to next catch)
}).catch(error => { // (**) second catch
alert(`The unknown error has occurred: ${error}`);
// don't return anything => execution goes the normal way
});
In the above example we see the first catch (*) will catch the error but can’t handle it (e.g. it only knows how to handle URIError), so it throws it again. The execution jumps from the first catch (*) to the next one (**) down the chain.
Why can't I just throw an Error inside the catch callback and let the process handle the error as if it were in any other scope?
If I don't do console.log(err) nothing gets printed out and I know nothing about what happened. The process just ends...
Example:
function do1() {
return new Promise(function(resolve, reject) {
throw new Error('do1');
setTimeout(resolve, 1000)
});
}
function do2() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
reject(new Error('do2'));
}, 1000)
});
}
do1().then(do2).catch(function(err) {
//console.log(err.stack); // This is the only way to see the stack
throw err; // This does nothing
});
If callbacks get executed in the main thread, why does the Error get swallowed by a black hole?
As others have explained, the "black hole" is because throwing inside a .catch continues the chain with a rejected promise, and you have no more catches, leading to an unterminated chain, which swallows errors (bad!)
Add one more catch to see what's happening:
do1().then(do2).catch(function(err) {
//console.log(err.stack); // This is the only way to see the stack
throw err; // Where does this go?
}).catch(function(err) {
console.log(err.stack); // It goes here!
});
A catch in the middle of a chain is useful when you want the chain to proceed in spite of a failed step, but a re-throw is useful to continue failing after doing things like logging of information or cleanup steps, perhaps even altering which error is thrown.
Trick
To make the error show up as an error in the web console, as you originally intended, I use this trick:
.catch(function(err) { setTimeout(function() { throw err; }); });
Even the line numbers survive, so the link in web console takes me straight to the file and line where the (original) error happened.
Why it works
Any exception in a function called as a promise fulfillment or rejection handler gets automatically converted to a rejection of the promise you're supposed to return. The promise code that calls your function takes care of this.
A function called by setTimeout on the other hand, always runs from JavaScript stable state, i.e. it runs in a new cycle in the JavaScript event loop. Exceptions there aren't caught by anything, and make it to the web console. Since err holds all the information about the error, including the original stack, file and line number, it still gets reported correctly.
Important things to understand here
Both the then and catch functions return new promise objects.
Either throwing or explicitly rejecting, will move the current promise to the rejected state.
Since then and catch return new promise objects, they can be chained.
If you throw or reject inside a promise handler (then or catch), it will be handled in the next rejection handler down the chaining path.
As mentioned by jfriend00, the then and catch handlers are not executed synchronously. When a handler throws, it will come to an end immediately. So, the stack will be unwound and the exception would be lost. That is why throwing an exception rejects the current promise.
In your case, you are rejecting inside do1 by throwing an Error object. Now, the current promise will be in rejected state and the control will be transferred to the next handler, which is then in our case.
Since the then handler doesn't have a rejection handler, the do2 will not be executed at all. You can confirm this by using console.log inside it. Since the current promise doesn't have a rejection handler, it will also be rejected with the rejection value from the previous promise and the control will be transferred to the next handler which is catch.
As catch is a rejection handler, when you do console.log(err.stack); inside it, you are able to see the error stack trace. Now, you are throwing an Error object from it so the promise returned by catch will also be in rejected state.
Since you have not attached any rejection handler to the catch, you are not able to observe the rejection.
You can split the chain and understand this better, like this
var promise = do1().then(do2);
var promise1 = promise.catch(function (err) {
console.log("Promise", promise);
throw err;
});
promise1.catch(function (err) {
console.log("Promise1", promise1);
});
The output you will get will be something like
Promise Promise { <rejected> [Error: do1] }
Promise1 Promise { <rejected> [Error: do1] }
Inside the catch handler 1, you are getting the value of promise object as rejected.
Same way, the promise returned by the catch handler 1, is also rejected with the same error with which the promise was rejected and we are observing it in the second catch handler.
I tried the setTimeout() method detailed above...
.catch(function(err) { setTimeout(function() { throw err; }); });
Annoyingly, I found this to be completely untestable. Because it's throwing an asynchronous error, you can't wrap it inside a try/catch statement, because the catch will have stopped listening by the time error is thrown.
I reverted to just using a listener which worked perfectly and, because it's how JavaScript is meant to be used, was highly testable.
return new Promise((resolve, reject) => {
reject("err");
}).catch(err => {
this.emit("uncaughtException", err);
/* Throw so the promise is still rejected for testing */
throw err;
});
According the spec (see 3.III.d):
d. If calling then throws an exception e,
a. If resolvePromise or rejectPromise have been called, ignore it.
b. Otherwise, reject promise with e as the reason.
That means that if you throw exception in then function, it will be caught and your promise will be rejected. catch don't make a sense here, it is just shortcut to .then(null, function() {})
I guess you want to log unhandled rejections in your code. Most promises libraries fires a unhandledRejection for it. Here is relevant gist with discussion about it.
Yes promises swallow errors, and you can only catch them with .catch, as explained more in detail in other answers. If you are in Node.js and want to reproduce the normal throw behaviour, printing stack trace to console and exit process, you can do
...
throw new Error('My error message');
})
.catch(function (err) {
console.error(err.stack);
process.exit(0);
});
I know this is a bit late, but I came across this thread, and none of the solutions were easy to implement for me, so I came up with my own:
I added a little helper function which returns a promise, like so:
function throw_promise_error (error) {
return new Promise(function (resolve, reject){
reject(error)
})
}
Then, if I have a specific place in any of my promise chain where I want to throw an error (and reject the promise), I simply return from the above function with my constructed error, like so:
}).then(function (input) {
if (input === null) {
let err = {code: 400, reason: 'input provided is null'}
return throw_promise_error(err)
} else {
return noterrorpromise...
}
}).then(...).catch(function (error) {
res.status(error.code).send(error.reason);
})
This way I am in control of throwing extra errors from inside the promise-chain. If you want to also handle 'normal' promise errors, you would expand your catch to treat the 'self-thrown' errors separately.
Hope this helps, it is my first stackoverflow answer!
Listen for unhandled errors:
window.addEventListener('unhandledrejection', e => {
// ...
});
window.addEventListener('error', e => {
// ...
});
If error gets swallowed, use self.report(error):
.catch(error => {
self.reportError(error);
});
https://developer.mozilla.org/en-US/docs/Web/API/reportError