Cleaner Exception Handling in JavaScript
Exceptions are when something exceptional or out of the ordinary happens, like a FileNotFoundException from a file IO library and undefined_column from a database library. Normally we write to catch these exceptions or pass them up the flow of the program so that the user can be notified.
What’s to follow is a suggestion of a cleaner way of handling exceptions. We do this by implementing a functional programming monad called Either.
Functional Programming is a programming paradigm where programs are constructed by applying and composing functions. So we give a function zero to many inputs and it returns one output.
Exception handling or catching is the process of responding to the occurrence of exceptions (breaks the normal flow of execution due to errors). To prevent your program from terminating abruptly, we need to catch exceptions.
Let’s say a function performs some side effect operation like performing some IO communication with an API, database, filesystem, etc. There is a possibility that an error will occur and an exception will be thrown.
Let’s say we have 2 services, a database, and a notification service.
To use these services we need to use them within a try-catch as follows:
function notify(message) {
try {
let response = NotificationService.send(message);
return response;
} catch (err) {
throw new Error("Notification Service Error");
}
}
function saveData() {
try {
let response = DB.query("insert into ...");
return response;
} catch (err) {
throw new Error("Database Error");
}
}
The notify and saveData functions have 2 return paths. Either it returns the expected response or throws an exception when something unexpected happens. The calling code needs to know that these functions might throw an exception. This is a problem in itself as JavaScript does not force you to declare that the function might throw an error. We have to know or look inside the function.
Instead of throwing another exception let’s return an error to only allow for only one return path.
function notify(message) {
try {
let response = NotificationService.send(message);
return response;
} catch (err) {
return err;
}
}
function saveData() {
try {
let response = DB.query("insert into ...");
return response;
} catch (err) {
return err;
}
}
This further complicates things because the shape of the return result is inconsistent. The function now returns data or an error. The calling code would need to do some sort of type checking to determine if the function returned an error or the expected result of data.
Let’s try something else. Changing the shape of the return might help?
function notify(message) {
try {
let response = NotificationService.send(message);
return { error: null, result: response };
} catch (err) {
return { error: err, result: null };
}
}
function saveData() {
try {
let response = DB.query("insert into ...");
return { error: null, result: response };
} catch (err) {
return { error: err, result: null };
}
}
This is an improvement as we only need to check for truthy or falsy values.
Let’s try a more elegant and functional way. Let’s use a monad.
In functional programming, a monad is a type that wraps another type in a standard consistent way. It also has a standard way of unwrapping that type.
In our case, we’ll use Either monad of the monet package(there are many more like this out there)
What does the “Either” monad do?
Either (or the disjunct union) is a type that can either hold a value of type A or a value of type B but never at the same time (Left or Right). Let’s change our return statements once more.
import { Either } from "monet";
function notify(message) {
try {
let response = NotificationService.send(message);
return Either.Right(response);
} catch (err) {
return Either.Left(err);
}
}
function saveData() {
try {
let response = DB.query("insert into ...");
return Either.Right(response);
} catch (err) {
return Either.Left(err);
}
}
We now use Either to wrap the response in the Right box and error in the Left box.
Let’s check out how we would now call these functions:
saveData()
.chain(() => notify("Data Saved"))
.map((data) => {
//do something with Right result
})
.leftMap((err) => {
//do something with Left error
});
Because these functions now return monads, we can now chain them. The nice thing about this method is that if an error occurs anywhere in the chain, the flow will abort just returning that error as illustrated by a programming concept called railway orientated programming.

After we call these functions, either we unwrap the Right using the map block or the Left using the leftMap block but never will they execute at the same time.
Conclusion:
Using a monad like Either allows us to more elegantly handle exceptions in a more functional way.