Bugs are inevitable in a program. Some bugs are caused by a programmer mistake while others could be as a result of a third party system or API that our program communicates with. The ability of our program to catch errors and recover instead of crashing cannot be over emphasised.
Often,problem surface when a program encounters a situation that the programmer didn't originally considered. Some programming mistake can range from simple typos that cause the computer to complain as soon as it lays eyes on our program to subtle mistakes in our understanding of the way the program operates, causing incorrect outcomes only in specific situations. Bugs of the latter can take weeks to diagnose. JavaScript is a bit forgiving and accommodating some weird operations without complain.
//such is allowed in Javascript
const x = false * "jamie"
//this gives an output of NaN. that can bubble through our //programs until it encounters an operation that makes our //program blow up
`
Strict Mode
Javascript can be made a little more strict by enabling strict mode. Enabling strict mode is done by putting the string "use strict" at the top of a file or a function body.
`
function spotTheError(){
"use strict"
for ( counter=0; counter < 10; counter++){
console.log("hello" , counter)
}
}
//run the function
spotTheError
`
Running this function throws a ReferenceError:counter is not defined. But if we run this particular function minus the word "use strict" javascript quietly creates a global and uses that. The reverse is the cause for strict mode when an error is reported instead.
In strict mode the this binding holds the value undefined in functions that are not called as methods. When such call is made outside of strict mode, this refers to the global scope object.
`
//sample code example
function whatIsThis(){
"use strict"
console.log(this)
}
function whatIsThisTwo(){
console.log(this)
}
`
Both functions above will log different value of this to the console. Accidentally calling a method or constructor incorrectly in strict mode; Javascript will produce an error as soon as it tries to read something from this, rather than happily working with the global object, creating and reading global variables.
Take a look at the code below which calls a constructor without the new keyword so that this will not refer to a newly constructed object.
`
//constructor function
function Person(name) { this.name = name }
const Jamie = Person("Jamie"); //oops forgot the new keyword
console.log(name);
//jamie
`
This incorrect call to the Person constructor function succeeds but returned an undefined value and created the global variable name. In strict mode, the result is different.
`
"use strict"
function Person(name) { this.name = name }
const Jamie = Person("Jamie"); //oops forgot the new keyword
//TypeError caannot set property 'name' of undefined.
`
We are immediately told that something is wrong instead of creating a global variable.
Handling Exceptions
When a function cannot proceed normally, what we would like to do is just stop what we are doing and immediately jump back to a place that knows howto handle the problem. Raising an exception resembles a super-charged return from a function. It exit out of not just the current function but also out of it's callers all the way down to the first call that started the execution. This is called unwinding the stack. The throw keyword is used to raise an exception. An exception is caught by wrapping a piece of code in a try block followed by the keyword catch. When the code in the try block causes an exception to be raised, the catch block is evaluated.
`
function howOld( age ){
if ( age < 30 ) return "Gen Z";
if ( age > 30 ) return "Agbalagba";
throw new Error("invalid age : ",age );
}
function lookUpAge(){
if ( howOld(34) ==== "Gen Z"){
return "Twitter"
}else{
return "Facebook"
}
}
try{
lookUpage()
} catch(error){
console.log("something went wrong");
}
`
A try catch block also have a finally block which gets run no matter what happens.
`
try{
} catch(e){
} finally{
//this blocks get called no matter what
}
`
When a catch body is entered, all we know is that something in our try body caused an exception. But we don't know what or which exception it caused. Javascript does not provide direct support for selectively catching exceptions; either you catch them all or you don't catch any. This makes it very easy to assume that the exception you get is the one you were thinking about when you wrote the catch block.
Wrong way of catching exception
As a general rule, don't blanket catch exceptions unless it is for the purpose of "routing" to tell another system that our application crashed. instead we should define a new Error object or Class and make use of the instanceof property of the Error Object to catch a specific error.
Selective Catching of Error
As explained above Javascript does not offer a mechanism for the selective catching of errors. It allows all errors to be caught or no error caught at all. An unhandled exception blows it's way down to the bottom of the call stack and it is handled by the environment that Javascript is running on. The browser displays unhandled errors in the browser console while Node terminates the program when an unhandled exception is encountered.
You might anticipate where the likely errors might occur and decides to catch that particular error. The dis merit of this method is that the program might contain a salient bug which might be difficult to debug because our catch block made use of the generic error message while discarding the original error object.
`
//code showing generic error catching
function cookDinner(){
const ingredients = prompt("what are you cooking");
if ( ingredients ==== "spice" ) return "jollof rice"
if ( ingredients === "stew" ) return "white rice"
throw new Error(`Invalid ingredients: ${ingredients}`)
}
//running our function and catching any exception
try {
let result = cookDinner();
if ( result ) return;
} catch (e){
//exception caught
console.log("your ingredient is wrong");
}
`
A lot could go wrong when exception is handled this way using a generic error. We could have a typo in our function call but the error message will still show the generic message making debugging of the exact problem difficult. The solution to above is to catch any specific error we might be interested in and still throw the original error exception. Assuming we want to create an InputError class which will inherits from the Error constructor object.
`
class InputError extends Error {
constructor(message){
super();
this.message = message;
}
}
This InputError will be thrown inside our cookFood function instead of the standard Javascript Error Object.
function cookDinner(){
const ingredients = prompt("what are you cooking");
if ( ingredients ==== "spice" ) return "jollof rice"
if ( ingredients === "stew" ) return "white rice"
//replace the below line
throw new Error(`Invalid ingredients: ${ingredients}`)
//with the InputError class
throw new InputError(`Invalid ingredients: ${ingredients}`);
}
//then we can look out for our InputError in the catch block
try {
let result = cookDinner();
if ( result ) return;
} catch (e){
//exception caught
if ( e instanceof InputError ) {
//do something with Input Error here
}
//we can still throw our original exception
else {
throw e
}
}
`
Summary
Strict mode will prevent Javascript acting in a weird way.It is recommended that we enable strict mode in our program. Never blanket-catch all errors ,it might cause you severe pain down the line. Error handling code is necessary only at the point where the error occurs and at the point where it is handled.
Thanks for reading......
Top comments (3)
Thanks, Jemie!
super()
should be called insideconstructor()
.throw new Error(`Invalid ingredients: ${ingredients}`)
Thanks Mike for the correction. I have updated the post with your correction. Catching error by displaying the exact error message to the user or UI is not the best strategy sometimes. The error message from the code is meant for the developer so that the problem can be fixed. It is best to selectively catch errors by display user friendly message to your User Interface. The error might show some inner workings of your system that you won't want a user to ever see.
Handling errors with grace is such a pain-point, but one important to address. Thanks for the post, I definitely learned a thing or two along the way.