This question already has answers here:
Catch all unhandled javascript promise rejections
(4 answers)
Closed 7 years ago.
A while back, v8 gained the capability to detect Promises that are rejected but have no handlers attached (commit). This landed in Chrome as a nice console error, especially useful for when you've made a typo or forget to attach a handler:
I would like to add a handler to take some action (e.g., reporting to an error reporting service) when this happens, similar to the uncaught exception pattern:
window.addEventListener("error", handler);
Alternatively, I'm looking for any mechanism that I can use to automatically invoke some sort of callback when a promise is rejected but not handled on that tick.
Until window.addEventListener('unhandledrejection', e => ...) is here you may hack your own Promise constructor which creates original Promise and calls catch on it passing:
error => {
var errorEvent = new ErrorEvent('error', {
message: error.message,
error: error
});
window.dispatchEvent(errorEvent); // For error listeners.
throw error; // I prefer to see errors on the console.
}
But it seems we have to patch then, catch and Promise.reject also -- lots of work.
Someone may want to write a polyfill to emit custom unhandledrejection event in such cases.
Related
My question consist of two parts:
Part 1
According to standard ES6 Promise I see that I forced to use catch block everywhere, but it looks like copy/paste and looks weird.
Example:
I have some class which makes request to backend (lets call it API class).
And I have a few requirements for API class using:
1) I need to make requests in different parts of my application with single request errors processing:
// somewhere...
api.getUser().then(... some logic ...);
// somewhere in another module and in another part of app...
api.getUser().then(... some another logic...);
2) I want so 'then' blocks would work ONLY when 'getUsers' succeeded.
3) I don't want to write catch block everywhere I use api.getUsers()
api.getUser()
// I don't want following
.catch(e => {
showAlert('request failed');
})
So I'm trying to implement single error processing inside of the class for all "users requests"
class API {
getUser() {
let promise = makeRequestToGetUser();
promise.catch(e => {
showAlert('request failed');
});
return promise;
}
}
...but if request fails I still forced to use catch block
api.getUser()
.then(... some logic...)
.catch(() => {}) // <- I forced to write it to avoid of “Uncaught (in promise)” warning
... otherwise I'll get “Uncaught (in promise)” warning in console. So I don't know the way of how to avoid of .catch block everywhere I use api instance.
Seems this comes from throwing error in such code:
// This cause "Uncaught error"
Promise.reject('some value').then(() => {});
May be you can say 'just return in your class "catched" promise'.
class API {
getUser() {
return makeRequestToGetUser().catch(e => {
showAlert('request failed');
return ...
});
}
}
...but this contradict to my #2 requirement.
See this demo: https://stackblitz.com/edit/promises-catch-block-question
So my 1st question is how to implement described logic without writing catch block everywhere I use api call?
Part 2
I checked if the same API class implementation with Q library will get the same result and was surprised because I don't get “Uncaught (in promise)” warning. BTW it is more expectable behavior than behavior of native ES6 Promises.
In this page https://promisesaplus.com/implementations I found that Q library is implementation of Promises/A+ spec. But why does it have different behavior?
Does es6 promise respects Promises/A+ spec?
Can anybody explain why these libraries has different behavior, which one is correct, and how implement mentioned logic in case if "ES6 Promises implementation" is correct?
I see that I forced to use catch block everywhere
No, you don't need to do that. Instead, return the promise created by then to the caller (and to that caller's caller, and...). Handle errors at the uppermost level available (for instance, the event handler that started the call sequence).
If that's still too many catchs for you, you can hook the unhandledrejection event and prevent its default:
window.addEventListener('unhandledrejection', event => {
event.preventDefault();
// You can use `event.reason` here for the rejection reason, and
// `event.promise` for the promise that was rejected
console.log(`Suppressed the rejection '${event.reason.message}'`);
});
Promise.reject(new Error("Something went wrong"));
The browser will trigger that event prior to reporting the unhandled rejection in the console.
Node.js supports this as well, on the process object:
process.on('unhandledRejection', error => {
// `error` is the rejection reason
});
Note that you get the reason directly rather than as a property of an event object.
So I don't know the way of how to avoid of .catch block everywhere I use api instance.
Surely the caller of getUser needs to know it failed? I mean, if the answer to that is really "no, they don't" then the event is the way to go, but really the code using api should look like this:
function useTheAPI() {
return getUser()
.then(user => {
// Do something with user
});
}
(or the async equivalent) so that the code calling useTheAPI knows that an error occurred; again, only the top-level needs to actually handle the error.
Can anybody explain why these libraries has different behavior, which one is correct, and how implement mentioned logic in case if "ES6 Promises implementation" is correct?
Both are correct. Reporting unhandled exceptions entirely in userland (where libraries live) is hard-to-impossible to do such that there aren't false positives. JavaScript engines can do it as part of their garbage collection (e.g.: if nothing has a reference to the promise anymore, and it was rejected, and nothing handled that rejection, issue the warning).
I'm currently reading "YKDJS - Async & Performance" by Kyle Simpson, in particular Chapter 3 - Promises.
The author says that any Promise for which no rejection handler is registered receives a default one:
let p = new Promise(function(resolve, reject) { resolve("Yay!"); });
p.then(
function(val) { /* Do whatever */ }
/* Default rejection handler
, function(e) { throw e; } */
);
Later in the chapter he claims that one problem with the way Promises are designed is the following:
In any Promise chain, any error that happens in one of the handler functions of the last Promise in the chain is just "swallowed" instead of being reported.
He proposes to change the way Promises work so that any Promise that doesn't have a rejection handler reports the error by throwing a global error. Then he proposes a theoretical Promise#defer() function one could use on a Promise to prevent this reporting behavior.
Now my question is: How do these two go together? It's true that any Promisethat doesn't have a rejection handler receives a default one which just throws the rejection value as a global error:
Promise.reject("Oops");
/* VM668:1 Uncaught (in promise) Oops */
So already Promises seem to work in just the way he proposes.
Am I misunderstanding something? Thanks for any help.
The Uncaught Handling he mentions in
Some Promise libraries have added methods for registering something
like a "global unhandled rejection" handler, which would be called
instead of a globally thrown error. But their solution for how to
identify an error as "uncaught" is to have an arbitrary-length timer,
say 3 seconds, running from time of rejection. If a Promise is
rejected but no error handler is registered before the timer fires,
then it's assumed that you won't ever be registering a handler, so
it's "uncaught."
In practice, this has worked well for many libraries, as most usage
patterns don't typically call for significant delay between Promise
rejection and observation of that rejection.
has been standardised as unhandled rejection warnings (not using an arbitrary timer, but firing right away). It does indeed work quite well.
He also states in Chapter 4 of ES6 & Beyond
[When] we are not listening for that rejection, […] it will be
silently held onto for future observation. If you never observe it by
calling a then(..) or catch(..), then it will go unhandled. Some
browser developer consoles may detect these unhandled rejections and
report them, but this is not reliably guaranteed; you should always
observe promise rejections.
I have just installed Node v7.2.0 and learned that the following code:
var prm = Promise.reject(new Error('fail'));
results in this message:;
(node:4786) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: fail
(node:4786) 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.
I understand the reasoning behind this as many programmers have probably experienced the frustration of an Error ending up being swallowed by a Promise. However then I did this experiment:
var prm = Promise.reject(new Error('fail'));
setTimeout(() => {
prm.catch((err) => {
console.log(err.message);
})
},
0)
which results in:
(node:4860) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: fail
(node:4860) 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.
(node:4860) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
fail
I on basis of the PromiseRejectionHandledWarning assume that handling a Promise rejection asynchronously is/might be a bad thing.
But why is that?
Note: See 2020 updates below for changes in Node v15
"Should I refrain from handling Promise rejection asynchronously?"
Those warnings serve an important purpose but to see how it all works see those examples:
Try this:
process.on('unhandledRejection', () => {});
process.on('rejectionHandled', () => {});
var prm = Promise.reject(new Error('fail'));
setTimeout(() => {
prm.catch((err) => {
console.log(err.message);
})
}, 0);
Or this:
var prm = Promise.reject(new Error('fail'));
prm.catch(() => {});
setTimeout(() => {
prm.catch((err) => {
console.log(err.message);
})
}, 0);
Or this:
var caught = require('caught');
var prm = caught(Promise.reject(new Error('fail')));
setTimeout(() => {
prm.catch((err) => {
console.log(err.message);
})
}, 0);
Disclaimer: I am the author of the caught module (and yes, I wrote it for this answer).
Rationale
It was added to Node as one of the Breaking changes between v6 and v7. There was a heated discussion about it in Issue #830: Default Unhandled Rejection Detection Behavior with no universal agreement on how promises with rejection handlers attached asynchronously should behave - work without warnings, work with warnings or be forbidden to use at all by terminating the program. More discussion took place in several issues of the unhandled-rejections-spec project.
This warning is to help you find situations where you forgot to handle the rejection but sometimes you may want to avoid it. For example you may want to make a bunch of requests and store the resulting promises in an array, only to handle it later in some other part of your program.
One of the advantages of promises over callbacks is that you can separate the place where you create the promise from the place (or places) where you attach the handlers. Those warnings make it more difficult to do but you can either handle the events (my first example) or attach a dummy catch handler wherever you create a promise that you don't want to handle right away (second example). Or you can have a module do it for you (third example).
Avoiding warnings
Attaching an empty handler doesn't change the way how the stored promise works in any way if you do it in two steps:
var prm1 = Promise.reject(new Error('fail'));
prm1.catch(() => {});
This will not be the same, though:
var prm2 = Promise.reject(new Error('fail')).catch(() => {});
Here prm2 will be a different promise then prm1. While prm1 will be rejected with 'fail' error, prm2 will be resolved with undefined which is probably not what you want.
But you could write a simple function to make it work like a two-step example above, like I did with the caught module:
var prm3 = caught(Promise.reject(new Error('fail')));
Here prm3 is the same as prm1.
See: https://www.npmjs.com/package/caught
2017 Update
See also Pull Request #6375: lib,src: "throw" on unhandled promise rejections (not merged yet as of Febryary 2017) that is marked as Milestone 8.0.0:
Makes Promises "throw" rejections which exit like regular uncaught errors. [emphasis added]
This means that we can expect Node 8.x to change the warning that this question is about into an error that crashes and terminates the process and we should take it into account while writing our programs today to avoid surprises in the future.
See also the Node.js 8.0.0 Tracking Issue #10117.
2020 Update
See also Pull Request #33021: process: Change default --unhandled-rejections=throw (already merged and released as part of the v15 release - see: release notes) that once again makes it an exception:
As of Node.js 15, the default mode for unhandledRejection is changed to throw (from warn). In throw mode, if an unhandledRejection hook is not set, the unhandledRejection is raised as an uncaught exception. Users that have an unhandledRejection hook should see no change in behavior, and it’s still possible to switch modes using the --unhandled-rejections=mode process flag.
This means that Node 15.x has finally changed the warning that this question is about into an error so as I said in 2017 above, we should definitely take it into account while writing our programs because if we don't then it will definitely cause problems when upgrading the runtime to Node 15.x or higher.
I assume that handling a Promise rejection asynchronously is a bad thing.
Yes indeed it is.
It is expected that you want to handle any rejections immediately anyway. If you fail to do (and possibly fail to ever handle it), you'll get a warning.
I've experienced hardly any situations where you wouldn't want to fail immediately after getting a rejection. And even if you need to wait for something further after the fail, you should do that explicitly.
I've never seen a case where it would be impossible to immediately install the error handler (try to convince me otherwise). In your case, if you want a slightly delayed error message, just do
var prm = Promise.reject(new Error('fail'));
prm.catch((err) => {
setTimeout(() => {
console.log(err.message);
}, 0);
});
I don't want to do anything if the promise is rejected, like getPromise().then(foo=>{});. Why is it an error on Chrome?
(new Promise((resolve, reject)=>{reject()}))
Promise {[[PromiseStatus]]: "rejected", [[PromiseValue]]: undefined}
VM3250:2 Uncaught (in promise) undefined
On Node and Firefox, it is OK to ignore the rejected part.
Promise rejection is like uncaught exception.
if you want to ignore exception - catch it, but don't handle,
same here - add .catch statement, but do nothing
Promise.reject(new Error('bad..')).catch(e => {})
I would not however recommend that, as promises reject for a reason, so you might want to add some sort of handling logic
Without a promise reject handler, chrome would throws reason of the rejection asynchronously that doesn't effects any JavaScript process, just prints the reason to console. If you get annoyed with this, you need to add handler, e.g. Promise.reject(reason).catch(reason => {}).
UPDATE: Why is an error? It may be because rejection is conventionally considered as an exception (an error).
P.S. Your "question" look like a feedback rather than a question. I personally prefer getting the reason as an error.
Rejecting a promise basically means something bad happened, and that you should be handling it with a call to catch. If you reject a promise without having a catch, it will throw an exception (more accurately, an unhandled rejection)
var a = (new Promise((resolve, reject) => {
reject("my error")
})).catch((err) => {
console.error(err)
})
I'm guessing it is a specificity of V8 if this happens only under Chrome, but it makes sense to me
Okay, I may be just missing the obvious, but I can't seem to find the general answer to this, and my Google-Fu has so far failed me.
In a Catch handler of a Promise, how do you re-throw the error, while still keeping the Promise stack trace of the original error?
That perhaps is not the correct description, so here is an example:
https://jsfiddle.net/8sgj8x4L/19/
With this code, the traced stack is:
Warning: a promise was rejected with a non-error: [object String]
at RejectWithAnError (https://fiddle.jshell.net/_display/:51:19)
at https://fiddle.jshell.net/_display/:57:14
From previous event:
at StartTheProgram (https://fiddle.jshell.net/_display/:56:6)
at window.onload (https://fiddle.jshell.net/_display/:67:1)
bluebird.js:1444 Unhandled rejection an error occurred
However, if a catch handler is added, and the error is re-rejected or re-thrown from that handler, then the stack becomes the location of the new Reject method call:
https://jsfiddle.net/8sgj8x4L/18/
Which traces this stack trace:
Warning: a promise was rejected with a non-error: [object String]
at https://fiddle.jshell.net/_display/:65:23
From previous event:
at StartTheProgram (https://fiddle.jshell.net/_display/:61:11)
at window.onload (https://fiddle.jshell.net/_display/:70:1)
bluebird.js:1444 Unhandled rejection an error occurred
You can see the inner method which dispatched the original error, "RejectWithAnError", disappears from the second stack, as the error was caught and re-thrown.
For reference, here is the complete code from the JSFiddle (The newest Bluebird is referenced as an external dependency):
window.P.longStackTraces();
function RejectWithAnError() {
var err = {error: true, message: "an error occurred"};
err.prototype = new Error();
return window.P.reject(err);
}
function StartTheProgram() {
return RejectWithAnError()
// Comment out this catch handler completely, and the Promise stack trace will correctly show the "RejectWithAnError" method as the error origin.
.catch(function (status) {
console.log("[WARN] Catch handler was called.");
// Neither of these lines will show "RejectWithAnError" in the Promise chain stack trace.
// throw status;
return window.P.reject(status);
});
}
StartTheProgram()
(On a side note, this is my first Stack Overflow question, so I hope this is the right format for this question.)
Edit: Updated example to reject using an object instance that inherits from a new Error instance.
Errors in JavaScript capture their trace when they are created so whenever you rethrow the error it will automatically capture the stack trace.
You are however not throwing errors. For this reason bluebird is giving you a warning (yay us). If you insist on throwing non-errors rather than properly subclassing errors - you'll need to manually trick the object to have a proper stack trace by manually capturing it. Either by creating a new Error inside the constructor and setting the .stack to its stack (might have to do some parsing) or by calling a specific method:
function RejectWithAnError() {
var err = {error: true, message: "an error occurred"};
// err.__proto__ = new Error(); -- this is how you'd do it with prototypes btw
// explicitly capture the stack trace of the object
if(Error.captureStackTrace) Error.captureStackTrace(err);
}
Fiddle here. Just a note, .prototype is a property used by functions to indicate the prototype of objects created by calling the function as a constructor. In order to set the prototype of an object directly you'd call __proto__ although that's rarely a particularly good idea. Here's an example with __proto__ instead of Error.captureStackTrace.
Promise.config({
warnings: false
});