Error Handling in Javascript
Errors in Javascript have two parts: a throw
and a try... catch
statement.
throw
creates an exception which will 'bubble up' through scopes until it either reaches a try... catch
block or crashes your program. Exceptions can be of any type:
throw "An error"
throw 500
throw false
throw new Error("This is an error")
The try... catch
block will capture an error from an inner scope. The finally
block runs after the try...catch
block regardless of if the error was triggered. One common usage of finally
is to handle default behaviour. For example, shutting down connections or closing files are often handled in finally
blocks, because they should always happen regardless of the success or failure of the operation.
function alwaysError() {
throw true
}
try {
alwaysError()
}
catch (e) {
// console.error is helpful for logging out errors
console.error(e)
}
finally {
console.log("Everything's under control.")
}
One Common Mistake
A common mistake for beginner programmers is to write try...catch
blocks to handle every possible error case. This is usually not the best way to deal with errors.
The example below shows an example of what you often shouldn't do. Can you see the problem?
function raiseToPower(num, pow) {
if (typeof num !== "number" || typeof pow !== "number") {
throw new Error("Number or power are not numbers.")
}
let result = num;
for (let i = pow; i > 0; i--) {
result = result * num
}
return result
}
let pow
try {
pow = raiseToPower("hello")
}
catch (e) {
console.error(e)
}
console.log(pow)
Instead of failing out of the process, we continue as if we successfully processed the user error. This means that instead of failing immediately, we set ourselves up for a later (and harder to debug) failure later in the program.
This example is a better example of where using a try... catch
block makes sense.
function readAFile(filePath) {
try {
openFile(filePath)
let fileContents = readFile(filePath)
closeFile(filePath)
return fileContents
}
catch {
throw new Error("File couldn't be opened.")
}
}
const paths = ["./file1","./file2","./file3"]
let myFile
for (let path of paths) {
try {
myFile = readAFile(path)
}
}
In this example we try to read each path in a list of files. If it fails to read one path, we throw an exception, but we don't really mind too much.
try...catch
blocks are best used when we want silent failures or managed errors, and therefore aren't a good choice for catching unanticipated bugs. They're a much better fit for predictable bugs that might happen due to factors outside our program, like expected files not existing.
Exercises
To read a file in Node.js we can use fs.readFile
:
const fs = require("node:fs")
const prompt = require("prompt-sync")()
const data = fs.readFileSync("path", "utf8")
Write a program which takes a space-separated list of files from the user and prints out the word count of each file. A word is a set of characters separated by whitespace.
If the program is unable to read any files, it should print out the files it's unable to read and continue adding to the wordcount.
For text you can use the standard Lorem Ipsum passage:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.