Bootcamp
Search…
0.8.2: Try Catch

Introduction

Try and catch is a JavaScript syntax (whose concept is common to many languages) that enables graceful error handling within applications. When an error happens in the try block, the catch block catches and handles that error. If there were no catch block to handle the error, the error would cause the program to crash.
Try and catch can be useful in both the frontend and the backend. In the frontend, we can use try/catch blocks to prevent erroneous API queries from crashing our website. When an erroneous API query happens, we can gracefully let our user know that something went wrong and to try another route. In the backend, we can use try/catch to prevent erroneous DB queries from crashing our server. Instead, we can gracefully return a 503 Server Error message instead of failing to respond to the client.
Throwing and catching errors is not directly related to Promises or async/await syntax from Module 4.1.3: Async Await, but try/catch syntax is a common control flow syntax used with Promises and async/await. Before we implement try/catch in the context of Promises let's see it on its own.

Try Catch History

Try/catch is fundamental to Javascript's ES5 language specification. It is a type of control flow akin to functions and conditionals.
We typically throw errors when we need to exit out of several layers of function calls in an "emergency". We typically catch errors when we need to ensure errors in our program or 3rd-party libraries don't cause the app to crash.

Exceptions

An "exception" is a type of control flow that halts code flow and exits out of successive parent functions until it is caught and handled by a catch block. If an exception is not handled, it causes the program to exit or crash. The goal of exceptions is to inform a higher level of the program of an error. throw is a statement that creates and "throws" or "fires" an exception.
For our use cases, we will use "exception" interchangeably with "error".

throw Examples

Single-Level throw Example

The following is an example Node app that throws an error.

index.js

console.log("about to throw!");
throw "throwed";
console.log("we threw");
Run the program with node.
node index.js
Notice how the output is different than if we had run a Node app without errors or even if we had a syntax error.
$ node index.js
about to throw!
​
/Users/akira/code/bootcamp/try-4.3.1/index.js:2
throw "throwed";
^
throwed
(Use `node --trace-uncaught ...` to show where the exception was thrown)

Multi-Level throw Example

It doesn't matter if throw is several levels deep in function calls. If uncaught, it will still end program execution. The following is an example Node app where throw is called from a nested function call and still ends the program.

index.js

console.log("define say");
​
const say = (word) => {
console.log("about to throw");
throw "throwed";
console.log("we threw");
​
// normal part of say function
console.log(word);
};
​
const yell = (word) => {
say(word.toUpperCase());
};
​
yell("hello world");

Catch Exceptions Using Try Catch

throw will end program execution at that line, except when we catch the thrown exception, for example in the following code snippet.
try {
throw "this is an error";
} catch (error) {
console.err(error);
}
The try/catch pattern is mostly used when writing code with several levels of function calls. Instead of returning an error-signaling value such as false up several levels we can throw an exception and return control of the application back to our catch statement.
The following example calls a hypothetical function myApp that invokes a number of nested functions that potentially throw errors. If any errors happen within those nested functions, we can catch and handle those errors gracefully from the module that called myApp.
try {
myApp(); // myApp calls dbQuery calls innerQuery calls outputFormat
// if there is an error, throw and go all the way back up here
} catch (error) {
console.err(error);
}

When to Use throw / try / catch as control flow

Catch a throw can happen from within a library.
Throw when you want to pass control back up several levels of function calls.
Throw when you want to prevent a promise from moving to the next .then
This example shows promise code that deals with a query that is empty. When the query returns no results on line 9 we want to prevent the next query from happening.
const categoryName = "vegan"; // from request.query
​
client
.query("SELECT * from categories WHERE name=$1", [categoryName])
.then((result) => {
// TODO: check if result.rows contains a row
console.log(result.rows[0]);
​
if (result.rows.length === 0) {
throw new Error("no categories found");
}
const categoryId = result.rows[0].id;
return client.query(
`SELECT
recipes.id,
recipes.label,
recipes.category_id AS recipe_category_id,
categories.id AS category_id,
categories.name AS category_name
FROM recipes
INNER JOIN categories
ON categories.id=recipes.category_id
WHERE category_id=$1`,
[categoryId]
);
})
.then((result) => {
console.log(`all recipes with category ${categoryName}`);
console.log(result.rows);
})
.catch((error) => {
if (error.message === "no categories found") {
// deal with not getting any categories
} else {
// this means it was a SQL syntax error, or something else.
console.err(error.stack);
}
});