Bluebird (or other Promise library) Keep Promise Error Stack Traces - javascript

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
});

Related

es6 promises swallow type errors

I want the browser to show an error message when a type error occurs.
errors like can not read property something of undefined or undefined reference.
new Promise(function(resolve,reject){
// do stuff ...
reject('something logical is wrong');
}).catch(e => console.error(e));
new Promise(function(resolve,reject){
// do stuff, and a syntax error :/
var a = { };
a.something.otherthing = 1; /* we have an error here */
// ...
}).catch(e => console.error(e));
In the first example the error is a logical one, and its fine to catch it in the catch(..) block.
But in the second example it is a clear development error, which happens all the time while developing new stuff. I don't want to catch it, i want the browser to show me the error like other errors in the console.
I want to be able to turn on chrome pause on exceptions and see the state of other variables. I want to see the stack trace in console.
I want it to act like a normal error.
Any idea?
Unlike exceptions in synchronous code, which become uncaught as soon as code returns to idle, a browser generally doesn't know the logical end of a promise-chain, which is where an asynchronous error could be considered uncaught. Chains are dynamically assembled after all, and therefore better be terminated with a final .catch at the logical end of the chain i.e. the asynchronous equivalent of idle.
Having a final .catch(e => console.error(e)) seems very reasonable to me, but you're right that browsers tend to display these errors differently from uncaught exceptions. If you want them to appear the same, you can use this trick:
.catch(e => setTimeout(() => { throw e; }))
This will throw e, containing the original stack trace and line number, on the very next cycle, and outside of the promise chain, where nothing will catch it and it will be reported as uncaught. We use setTimeout to overcome the default behavior of .catch which is to capture any exceptions in-chain in case you intend to keep on chaining.
With this, I hope you see that any differentiation between "logical" and other errors is irrelevant. Any error that makes it to the tail of the chain was fatal to the chain i.e. uncaught (though of course you can triage "logical" from other errors in the final catch and display them differently if you choose.)
chrome has an option Pause on Caught Exceptions in Sources tab, i enabled that option and Pause on Exceptions feature is working fine now.

Can you "plug in" to unhandled Promise rejections in Chrome? [duplicate]

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.

errors not being thrown after promise

i have a weird situation i would like to know how to solve.
in my app errors that happen after functions with promises do net get thrown and the app just stalls.
here an example:
getTenant = (req) ->
deferred = Q.defer()
deferred.resolve('foo') if req.info.referrer
deferred.resolve('bar') if !req.info.referrer
deferred.promise
Routes =[
{
method: 'GET'
path: '/'
handler: (request, reply) ->
getTenant(request).then (tenant) ->
console.log 'tenant', tenant
# here `User` is not defined and doesn't even exist
# why is there no error here?
if !User.isAuthorized(request, tenant)
reply 'not authorized'
else
reply 'authorized'
}
]
after getTenant i call a function on User.
User doesn't exist or is imported but the app gives me no error.
why is that?
of course if i wrap the code in a try/catch i catch the error but thats not the point. i would expect the code to actually break and throw the error.
here the full sample app: https://github.com/aschmid/hapierrortest
thank you,
andreas
The short answer is because you have neglected to include your error handler. Promises always require both success and error handling functions.
This is a fundamental (and generally good) aspect of promises. Inside a promise handler (success or error), any thrown errors do not hit the window. Instead, the promise implementation internally wraps promise handlers in a try block, and upon capture, rejects the promise returned by .then. Why? So that you can choose where and when and how to deal with errors. This is why you can put a single catch(errHandler) (which is sugar for .then(null, errHandler)) at the end of a long promise chain:
somePromise.then(function s1 (val1){
// use val1, return something (e.g. a new promise)
}).then(function s2 (val2){
// use val2, return something (e.g. a new promise)
}).then(function s3 (val3){
// use val3, return something (e.g. a new promise)
}).catch(function e1 (err1){
// handle e1 as you like (console.log, render something on the UI, etc.)
});
In the above chain, an error that occurs at somePromise, s1, s2, or s3 will be handled at e1. This is pretty nice, better than having to handle the potential error explicitly at every step of the chain. You still CAN handle the error at every step if you want, but it's not at all necessary.
Generally with promises you'll handle the error in some way, you don't WANT errors to crash your app after all. But if for some reason you wanted errors to be re-thrown out to the outer scope, you'd have to use a promise library with a final method that allows for re-throwing. For example, .done() in the Q library.
I'll start by saying that I've never used Hapi, but the syntax you have looks fine EXCEPT...
If req.info is null, you'd get an exception thrown. Try this
deferred.resolve('foo') if req.info && req.info.referrer
deferred.resolve('bar') if !req.info || !req.info.referrer

does promise catch too much errors?

I am writing promise following this style in the doc:
Q.fcall(promisedStep1)
.then(promisedStep2)
.then(promisedStep3)
.then(promisedStep4)
.then(function (value4) {
// Do something with value4
})
.catch(function (error) {
// Handle any error from all above steps
})
.done();
The catch clause will catch any errors including typos.
However, according to nodejs dos:
By the very nature of how throw works in JavaScript, there is almost never any way to safely "pick up where you left off", without leaking references, or creating some other sort of undefined brittle state. The safest way to respond to a thrown error is to shut down the process.
Some kinds of errors would throw out if we are writing code in the callback style, but not in promise style
This is really confusing me. How should I avoid leaking references when writing in promise.
Thanks~
The example shows good promise chain, including using .done() to make sure any unhandled exceptions are thrown from the promise chain to the outside application.
As far as references and error handling: the promise chains only guarantee than an error will be forwarded to the .catch callback. If there is no way to clean up the state when the error is thrown - you are out of luck. For example
Q.fncall(function firstStep() {
var fs = open file reference
foo.bar; // generates ReferenceError
}).then(function somethingElse() {
...
}).catch(function (err) {
// we have caught ReferenceError
// but we cannot clean up open fs reference!
}).done();
We caught the error, but the catch handler cannot close fs reference. This is what it means that even with promises we have to think how to clean up resources in case of an error.

Using Kris Kowal's Q. How should I catch if any errors have been thrown throughout the life of a chained promise?

I'm new to promises, and I just started using Kris Kowal's Q today (I've read about it before and used it a little with Angular.js), and I was doing an Ajax call with jQuery and turning it into a Q promise. I was trying to find a good way to have some sort of catch at the end that would tell me whether this promise was rejected at one point or not so I could tell the user something went wrong along the way.
The idea is I want to extend the idea to work no matter how long my promise and at the end I want a check to see if it failed at anytime during the promise. This is what I ended up writing, but I wouldn't want to throw a new error in each of my fails/catches like this in order to propogate the error down the promise chain. I want something less weird.
function deleteEvent(id){
var url = "sampleURL/deleteEvent?id=" + id;
return Q($.ajax({
type: 'POST',
url: url,
dataType: 'json'
})).then(function (data) { // success
// on success
UpdateDOMforEventsChanged();
}).fail(function (xhr) { // failure
console.log(xhr);
throw new Error('Problem deleting Event');
});
}
And here's how I extended this promise elsewhere
deleteEvent(event._id).then(function () { // AJAX call was a success delete the things
$('#calendar').fullCalendar('removeEvents', event._id);
}).done(null, function () { // It failed somewhere alert the user.
alert("There was an error with deleting the event. Check your network connection and try again.")
});
So basically I've noticed that if I take out the error that I'm throwing in the fail section of my deleteEvent promise that it executes the following then that comes after it in the extended promise i.e. it executes deleting the events client side from fullCalendar. I don't want it to do this if it fails, how can I fix this? Also is there a reason why it does this? I'm guessing it might be, because you can resolve the error somehow or set a default and continue the promise chain...is that right?
I also notice if I don't throw the error it skips over the fail callback function of the done method. I know this is because the q documentation says the following about it's .done() method,
If there is an unhandled rejection, either because promise is
rejected and no onRejected callback was provided, or because
onFulfilled or onRejected threw an error or returned a rejected
promise, the resulting rejection reason is thrown as an exception in a
future turn of the event loop.
So I'm assuming I can't use .done() to catch all rejections including handled ones, (and I'm guessing in the ajax error is handled since I have the catch/fail after it), but what alternative is there?
A handled rejection is like a caught exception. It stops propagating since well, it was handled. If you want to handle the rejection and keep it rejected you need to rethrow, again, just like in synchronous code.
try {
throw new Error();
} catch(e){
// handle error
}
// no error here this code will keep running.
If you want it to keep rejecting and handle it, you need to rethrow:
try {
throw new Error();
} catch(e){
// handle error
throw e;
}
// this code will not run
The same with promises, just like you wrote. This is not particularly odd about promises, this is how synchronous exceptions work as well. If you want to propagate - you re-throw, otherwise - the errors are considered handled.
If you don't throw in fail it means you recover from error to success (see: Rejection into success [use cursors] mind fail is alias for catch), so result promise ends as successful. It can't be other way
Instead of using fail see if Q provides function which provides access to result and returns self promise (something like aside in implementation I mantain).
Also your flow can be simpler with less then's, see:
function deleteEvent(id) {
return Q($.ajax({
....
})).then(function (data) { // success
// on success
UpdateDOMforEventsChanged();
}); // No need for error handler
};
deleteEvent(event._id).done(function () { // AJAX call was a success delete the things
$('#calendar').fullCalendar('removeEvents', event._id);
}, , function (e) { // It failed somewhere alert the user.
console.error(e.stack || e.message);
alert("There was an error with deleting the event. Check your network connection and try again.")
});

Categories

Resources