OK. I may be splitting hairs here, but my code isn't consistent and I'd like to make it so. But before I do, I want to make sure I'm going the right way. In practice this doesn't matter, but this has been bothering me for a while so I figured I'd ask my peers...
Every time I use a try... catch statement, in the catch block I always log a message to my internal console. However my log messages are not consistent. They either look like:
catch(err) {
DFTools.console.log("someMethod caught an error: ",err.message);
...
or:
catch(ex) {
DFTools.console.log("someMethod caught an exception: ",ex.message);
...
Obviously the code functions properly either way but it's starting to bother me that I sometimes refer to "errors" and sometimes to "exceptions". Like I said, maybe I'm splitting hairs but which is the proper terminology? "Exception", or "Error"?
This is a bit subjective, but to me an error is when someone or something does something wrong, improper, or invalid. It could be a syntax error, a logical error, a read error, user error, or even a social error. It's an abstract concept.
An exception, on the other hand, is an object that is created and thrown when a certain condition occurs in code. It may or may not correspond to a conceptual error. So to me, the proper nomenclature is "exception".
The ECMAScript specification calls them exceptions. You might want to do likewise.
To make your logging more informative:
catch(ex) {
DFTools.console.log("someMethod caught an exception of type "
+ ex.name + ": ", ex.message);
You might also want to bear in mind that exceptions (unfortunately) can be of any type, and so don't necessarily have name and message properties:
catch(ex) {
if (ex.message && ex.name) {
DFTools.console.log("someMethod caught an exception of type "
+ ex.name + ": ", ex.message);
} else /* deal with it somehow */
As this is starting to look pretty cumbersome to repeat everywhere, you might want to capture it in a function:
function logExceptions(methodName, action) {
try {
action();
} catch (ex) {
if (ex.message && ex.name) {
DFTools.console.log("someMethod caught an exception of type "
+ ex.name + ": ", ex.message);
} else {
DFTools.console.log("someMethod caught a poorly-typed exception: " + ex);
}
}
}
Now you can say:
logExceptions(function() {
// do some risky stuff...
});
In JavaScript it is called Error Catching. So I would suggest you use error instead of exception.
Leave the choice in the middle by using "e". Like in the examples of Mozilla.
Mozilla Core JavaScript 1.5 Reference
Exception is something you may expected for example in an attempt to open a file may face a "File not found exception". On the other hand, errors are something you may not see it coming like stack over flow or not enough memory.
An exception is an alternative logical way out off a function that does not produce a logical result. An exception also allows a better explanation of what happen why it exist this way. For File opening, again, a file handle is a logical result and if the file is not exist (one possible exception) or it is a folder not a file (another possible exception).
MAJOR DISCLAIMER: I don't consider that there is a "right" answer to this. The views expressed here are subjective and personal. What's more is that the ideas I'm about to espouse are only useful if you are going to do different things with different, ahem, faults... as you might using a system as per Daniel Earwicker's informative answer. With that in mind:
I contend that "an EXCEPTION is exceptional". An ERROR is less unexpected.
disclaimer: The following pseudo-code is not good; it merely serves as the minimum case I could think of to illustrate my point.
note: in this thought experiment, GetFile returns UNDEFINED if it cannot find the specified file.
function AlwaysGetFile(name){
var file = null;
if(FileExists(name)){
file = GetFile(name);
if(typeof file === "undefined"){
throw new "couldn't retrieve file" EXCEPTION
}
}
else{
throw new "file does not exist" ERROR
}
return file;
}
In the case that a consumer calls GetFileOrThrow with a filename that doesn't exist, an ERROR will occur. To my mind the distinction is really that higher-level code (or user input) is doing something wrong... this function must pass an ERROR up the line to that higher-level code which can decide what to do about this result. Consider it like this... this function would be saying to any consuming functions:
Look, my friend, I know what's going on here: it is an ERROR to request BobAccounts.xml, so don't do it again! Oh, and if you think you now know what might have gone wrong (having abused me), go ahead and try to recover from it!
Now consider the case that this function takes the name, checks that the file exists and then for some reason fails to retrieve it. This is a different situation. Something really unexpected has happened. What's more, the consuming code isn't to blame. Now we really want this function to say to any consuming functions:
Oh fiddlesticks! Sorry about this, I humbly beg your pardon but something EXCEPTIONAL that I don't really understand has gone wrong. I don't think that your request for BobAccounts.xml was unreasonable... and I know I should be fulfilling it for you. Since I'm lower level code than you, I really ought to know what's going on... but I don't... and since you've less chance than me of understanding this EXCEPTIONAL situation, I think you'd probably best just stop what you're doing and let this message go all the way to the top... I mean, there is something seriously fishy going on here.
So I suppose my summary is this: If the mistake happened in higher order code (you were passed bad data) throw an ERROR. If the mistake happened in lower order code (a function you depended on failed in a way you didn't understand and couldn't plan for) throw an EXCEPTION... and if the mistake happened in the function you are currently writing... well, duh, if you're aware of it then fix it!
And, finally, to answer the original question more directly: In terms of handling ERRORS and EXCEPTIONS, my advice would be: Handle all ERRORS gracefully (optionally logging them)... but handle EXCEPTIONS carefully indeed; only try to recover from an EXCEPTION if you are really sure you know what it is and why it's happened, otherwise let it bubble up (rethrowing it if you have to).
What you get in a Catch block is an Exception, so I name it as an exception...
If it is an error - I can handle it in my code & I usually don't expect to see it in the Catch block
HTH.
Related
I'm a JS game dev who's been trying to combat tampermonkey scripts for a while now.
I came up with a solution for people hooking into WebSockets where I'd cause the WebSocket to throw an error new WebSocket(0); (0 throws an error due to it being a number)
let output;
try {
output = new target(...args);
} catch(e) {
let source = e.stack.substring(e.stack.indexOf("("), 1 + e.stack.indexOf(")"));
e.stack = e.stack.replace(source, "nothing to see here");
throw e;
}
this code made the error's stack have all the information I was looking for replaced!
I've been looking at Object.defineProperty, and I was wondering how I could stop an error's stack from being modified before I have access to that specific error. And if anyone has any other ways I could stop a script from being loaded or run, I'd love to hear them!
One thing you could do is Object.freeze the error before throwing it. This would prevent people from altering the object's contents.
So for example:
try {
new WebSocket(0);
} catch (wsErr) {
throw Object.freeze(wsErr);
}
The code catching your error and trying to alter it would fail to be able to alter it. This should work as it will cause the code that was altering the error to throw with the following:
Cannot assign to read only property 'stack' of object ''
The other thing you'll have to consider is that in your code where you're catching the error, you will not be able to alter its contents either. Typically with errors, that's not a huge deal though. Tampering with errors is one of the only reasons I can think of for modifying the error.
I cannot find anything on this so I assume I am either searching for the wrong thing or this is not a common thing to do.
I am writing a basic library that abstracts a few underlying libraries. Each underlying library throws its own pretty ambiguous error messages that mean very little to the end user. Some are also extended with different properties.
Currently I catch these errors and throw a new error which makes more sense to the end user and are consistent in structure. However, this loses the stack trace from the original error and I want to preserve this as it contains some information valuable to the end user.
At first I thought a custom Error class was the way to to keep type checking, so I started with something like
class WrappedError extends Error {
readonly stack;
constructor(message: string, error?: Error) {
super(message);
if (error?.stack) {
this.stack = error.stack
}
}
}
but quickly realised that even if this works (EDIT: just checked, the trace does not continue) then the error.stack would only contain the trace from the original throw to my catch and creation of a new WrappedError - everything after would be missing.
This is making me think that my last realistic option is to mutate the original error and rethrow it
catch (error) {
error.message = `Error reason due to such and such, but also; ${error.message}`
throw error;
}
I'm not a massive fan of mutation. And also at this point I quite like the idea of having a single custom error type from my class.
Can stacks be preserved and continued? Or what is the best way to achieve this? Or is there a convention used in JS?
It seems a native solution was added somewhat recently, so a clean solution would be
try {
...
} catch (error: unknown) {
throw new Error('Error reason due to such and such', { cause: error });
}
We are starting to use RequireJS and Backbone.js. However, it is also important to gather errors that happen in production. For that we use errorception. We found that we don't get the stacktrace. Does anybody know of a way to fix that?
Actually, this was a false alarm. The reason that errorception did not show a stacktrace for a test that I did was because I executed:
throw 'foo bar';
The correct way was supposed to be:
throw new Error('foo bar');
I then tried to execute the following code without defining the variable a:
if (a === 5) {
console.log('Should not come here.');
}
And there was the stacktrace in errorception. We've been using errorception for quite some time now and it is very helpful.
UPDATE
[Rewriting question to focus on the problem I am trying to understand.]
Is there a means in JavaScript to throw Exceptions that notify the line number where the problem occurs? Similar to C#'s debugger, if an error is thrown on line 50 then I will be taken to line 50.
For example, according to MDN EvalError represents an error with eval(). So, let's say I have a function that uses eval(). I want to use a specific error that is representative of the problem at hand, EvalError:
//As written here the error implies there is a problem on this line. See Firebug console window
var evalErra = new EvalError('required element missing from evaluation');
var stringFunc = "a=2;y=3;document.write(x*y);";
EvalString(stringFunc);
function EvalString(stringObject) {
//Some arbitrary check, for arguments sake let's say checking for 'x' makes this eval() valid.
if(stringObject.indexOf('x') !== -1) {
throw evalErra;
//throw 'required element missing from evaluation';//This way offers no line number
}
eval(stringFunc);//The problem really lies in the context of this function.
}
If I'm going about this all wrong, then please tell me how I should approach these kinds of issues.
When you throw an error, execution of the current code will stop and JS will work its way back up the execution tree until it finds a catch () which handles the particular type of error being thrown, or gets all the way up to the top of the tree, causing an "unhandled exception" error: You threw an error, and nothing caught it, and now someone's window got broken.
try {
if (true) {
throw 'yup'
}
} catch (e) { // catches all errors
... handle the error
}
When doing error handling you want to do the following
throw new Error("message");
Then if you ever handle this error look at err.stack (firefox/opera/chrome) or err.line (Safari) or err.IE_Y_U_NO_SHOW_ME_ERROR_LINE_NUMBER (IE) to find the line number.
If you want you can subclass Error.
is it possible to register an error or exception handler/function that will be executed when a javascript error or exception occurs? I just feel wrapping all codes in try/catch block seems very tedious and inefficient.
window.onerror = function (msg, file, line) {
// handle error here
}
Supported by:
Chrome 13+
Firefox 6.0+
Internet Explorer 5.5+
Opera 11.60+
Safari 5.1+
Andy E's answer (+1) tells you how to do it.
That said, JavaScript isn't really meant to have caught execeptions in the same sense that, say, Java does. If your code is throwing exceptions, pull up a console and use the debugger to fix them. JS exceptions are slow, and really not meant to be used for flow control. A method won't throw an exception unless there's a serious problem — and it's usually a programming error.
Here's an alternative answer than the window.onerror solution. This isn't something I've used in production, but is something that I just like because of the flexibility (i.e. you could use it to debug things like timing how long a method took or something).
Whilst you could probably pass window into it (don't quote me on that, and don't think it's a good idea) it does work if you have all your methods in an object:
(function(obj) {
for (var name in obj) {
if (typeof(obj[name]) == 'function') {
currentMethod = obj[name];
obj[name] = function() {
try {
currentMethod();
}
catch (e) {
alert('Exception Handler: ' + e);
}
};
}
}
}(myObject));
Here's it working: http://jsfiddle.net/jonathon/kpYnW/
Basically, it goes through each property in your object, finds the ones that are functions and wraps them in a try/catch (or whatever else you want).
Whether or not it's efficient is a different matter - I've just found it a very useful technique for debugging. Unfortunately I can't remember the original place I read it but if anyone knows, please add as a comment.