Using the native (ES6) Promise. Should I reject with an Error:
Promise.reject(new Error('Something went wrong'));
Or should I just reject with a string:
Promise.reject('Something went wrong');
And what is the difference in browser behaviour?
Yes, it most definitely should. A string is not an error, when you have errors usually it means something went wrong which means you'd really enjoy a good stack trace. No error - no stack trace.
Just like with try/catch, if you add .catch to a thrown rejection, you want to be able to log the stack trace, throwing strings ruins that for you.
I'm on mobile so this answer is rather short but I really can't emphasize enough how important this is. In large (10K+ LoC) apps stack traces in rejections really made the difference between easy remote bug hunting and a long night in the office.
I'm recommending to use Error object only (not a string) for sending the reasons.
Justification
Other parts of code are generating the Errors inside the Promise rejection reason...
If some code fails the exception returns the Error object. Also if you will call any external library, which does not support the Promise, it will throw the Error object when something fails.
If one of errors mentioned above occurs inside the Promise, it will be transformed into catch with Error object.
Therefore if you will use the string as promise rejection reason, you have to expect that the catch can occurs with your string (part of your code) or Error (when some general error occurs). So you will have to use ugly code (err.message || err) everywhere, when you have to handle the error.
Related
I've inherited a codebase at work that contains a dozen or so examples of the following pattern:
var promise = null;
try {
promise = backendService.getResults(input);
}
catch (exception) {
console.err(exception);
}
if (promise !== null) {
promise.then(function (response) {
// do stuff
})
.catch(function (error) {
console.err(error);
});
}
Where backendService is an Angular service that in turn calls a REST service through $http.
So here's my question: is that try/catch really necessary? Will there ever be any scenario where a particular error/exception is thrown that the promise's .catch fails to catch?
This has been the subject of a bit of debate on the team all morning, and the only resolution we've come up with is that we don't think it's necessary, but (a) changing it breaks the tests that were written alongside it (which would also need to be changed), and (b) well... it's defensive coding, right? It's not a Bad Thing.
The merits of actually bothering to refactor it into oblivion when there are more important things to do aren't what I'm asking about, though. I just want to know if it's a reasonable pattern when promises are being passed around like this (in AngularJS specifically, if that makes a difference), or just paranoia.
Do promises in AngularJS catch every exception/error?
No. Only exceptions that are thrown from inside then/catch callbacks are automatically caught. All errors happening outside of them will need to be handled explicitly.
Will there ever be any scenario where a particular error/exception is thrown that the promise's .catch fails to catch?
Yes. That backendService.getResults(input) call might not return a promise, but it can throw an exception. Or it doesn't even get that far when backendService is null, and you'll get a ReferenceError or .getResults is not a function and you'll get a TypeError.
is that try/catch really necessary?
Not really. In the latter case, that your code has a grave mistake, you probably don't care to throw and crash. The former case, that backendService.getResults(input) throws, is heavily despised. Asynchronous functions should never throw but only return promises - exactly for the reason that you don't have to write two error handling statements then.
well... it's defensive coding, right? It's not a Bad Thing.
Yeah, pretty much. But it is questionable here. A synchronous exceptions is really unexpected here, not just a service whose failure can be handled. The logging message in the catch block should signify that.
Notice that it also isn't defensive enough. It doesn't catch the probably more likely mistake where getResults() does return, but something that is not a promise. Calling .then() on that might throw. Similarly, the if (promise !== null) is dubious, as it hides when null is returned erroneously (we really could need try-catch-else here).
is that try/catch really necessary?
Not really. As long as your backendService.getResults() returns a promise. Like return $http(...)
Will there ever be any scenario where a particular error/exception is
thrown that the promise's .catch fails to catch?
I don't think so, since any error will be a rejected promise, it will fall into your .catch() handler
well... it's defensive coding, right? It's not a Bad Thing.
It depends... Javascript try/catch is has some performance issues. So if you're using just for the sake of making sure, you could remove it :)
Take a further look at try-catch discussion here if you wish: Javascript Try-Catch Performance Vs. Error Checking Code
While doing some work I saw "uncaught exception" errors reported in Firebug appearing at seemingly random moments. The exceptions were thrown inside promises in background.
At first I was surprised the errors were reported at all, as my understanding was that errors thrown in promises are simply caught and passed along the callbacks chain. But they are reported in post-Opera (=Chrome) too, and they don't kill the script, so that is a good thing then.
But in Opera exceptions are reported immediately while in Firefox there is some seemingly random delay (from a couple of seconds to half a minute). Why is that?
Here is a test code:
var p = new Promise( okLater );
p.then( kill );
function okLater( pass, fail ) {
setTimeout( pass.bind( this, "O.K." ), 10 );
}
function kill() {
console.log( "Killing" );
throw "Oops"
}
At first I was surprised the errors were reported at all, as my understanding was that errors thrown in promises are simply caught and passed along the callbacks chain
Yes, they are indeed. That promise you have created via p.then(kill) is going to be rejected.
But we don't really want that. Exceptions that are silently ignored? Not a good idea. So we need unhandled rejection handling. An "unhandled exception" is a rejected promise that got no error handler attached. There is a bit of problem with identifying these, because the error handler could be attached later intentionally, and we wouldn't want that promise to be reported. Still, promise implementations have the capability to track their promises and report "possibly unhandled rejections", see this answer and How do I handle exceptions globally with native promises in node.js? for further details.
reported in Firebug appearing at seemingly random moments.
The moment you can be totally sure that a promise was rejected without a handler being attached is when it is garbage-collected. IIRC, Firefox has indeed implemented this hook, so these "random moments" would depend on the GC behaviour.
If I throw an error in a promise's catch handler, the error is never bubbled up!
Promise.resolve()
.then(function() { return foo.bar(); })
.then(function() { return console.log('ok!'); })
.catch(function(err) { return baz.quux(); });
In both Node v0.8 with the promise module and Chrome 36's JavaScript console, this prints nothing. I'm expecting to see ReferenceError: baz is not defined appear somewhere.
Shouldn't we see something? Is this a part of the promises spec that I missed?
UPDATE: Clarification: this happens in Chrome 36's V8 without any third-party module.
Yes, this is a problem promise implementations face. However the two you've chosen fail at it pretty bad. Indeed - the error is swallowed in your case and indeed you will not get any indication of this silent failure unless you attach a error handler yourself.
Your options are:
Use a library that offers .done like Q and manually append .done to every single promise in your code to indicate you will not attach handlers to it.
Use a library like Bluebird or When that does unhandled rejection detection.
In particular, Bluebird is faster than native promises and does this properly. It is also a superset of native promises so you can write code that uses that subset if you'd like (although it has a much richer API). The code you have above logs an unhandled rejection with Bluebird.
By the way, Firefox handles this much better and Firefox native promises detect unhandled rejections.
Recently, I've been read the TJ's blog article: "Farewell Node.js".
I'm not quite understand about the Node fails part. Here it is:
Error-handling in Go is superior in my opinion. Node is great in the sense that you have to think about every error, and decide what to do. Node fails however because:
you may get duplicate callbacks
you may not get a callback at all (lost in limbo)
you may get out-of-band errors
emitters may get multiple “error” events
missing “error” events sends everything to hell
often unsure what requires “error” handlers
“error” handlers are very verbose
callbacks suck
What specific problem is being referred to when the author writes "you may not get a callback at all (lost in limbo)"?
It means the error is lost in limbo since the operating function did not "get a callback", viz., the error is "swallowed", since there is no callback to handle it.
var foo = function(onSuccess, onFailure) {
// ...
// uh-oh, I failed
if(onFailure) {
onFailure(err);
}
else {
// well, that probably wasn't too important anyway...
}
}
foo(function() { console.log("success!"); } /* no second argument... */);
Note in synchronous coding (say, most Java) it's much harder for this to happen. Catch blocks are much better enforced and if an exception escapes anyway, it goes to the uncaught exception handler which by default crashes the system. It's like this in node too, except in the above paradigm where an exception isn't thrown it's likely swallowed.
Strong community convention could solve it in my trivial example above, but convention can not completely solve this in general. See e.g. the Q promise library which supports a done method.
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 done call there instructs the promise chain to throw any unhandled exceptions (if the catch block were missing, or the catch block itself throws). But it is fully the responsibility of the programmer to call done, as it must be, since only the programmer knows when the chain is complete. If a programmer forgets to call done the error will sit dangling in the promise chain. I have had real production bugs caused by this; I agree, it's a serious problem.
I'll be honest that a lot of that block in the post doesn't make much sense to me. But I'm an experienced Node.js programmer and this is the only thing I can think that could mean.
The word "possibly" suggests there are some circumstances where you can get this warning in the console even if you catch the error yourself.
What are those circumstances?
This is pretty well explained in the docs:
Unhandled rejections/exceptions don't really have a good agreed-on
asynchronous correspondence. The problem is that it is impossible to
predict the future and know if a rejected promise will eventually be
handled.
The [approach that bluebird takes to solve this problem], is to call a
registered handler if a rejection is unhandled by the start of a
second turn. The default handler is to write the stack trace to
stderr or console.error in browsers. This is close to what happens
with synchronous code - your code doesn't work as expected and you
open console and see a stack trace. Nice.
Of course this is not perfect, if your code for some reason needs to
swoop in and attach error handler to some promise after the promise
has been hanging around a while then you will see annoying messages.
So, for example, this might warn of an unhandled error even though it will get handled pretty well:
var prom = Promise.reject("error");
setTimeout(function() {
prom.catch(function(err) {
console.log(err, "got handled");
});
}, 500);