How to avoid try/catch statements nesting/chaining in JavaScript?
And how to make your code cleaner and look kind of sequential! With a simple, easy, and efficient error handling method.

💡Note: After some feedback from readers I would like to clarify some things. First, the following pattern is not a drop-in replacement for a classic try/catch error handling strategy. In fact, in some use cases, it can even be an anti-pattern. The way this article shows this pattern is as a complementary way to handle errors where you want to take direct action in response to an error. Finally, everything you’ll read here is subject to each person's programming style and there are many other patterns available. Thank you for your kindness 🙏🏻 Now let’s continue to the article
Every day developers are writing code that we need to check for potential errors.
“Try / Catch” statements are everywhere… and sometimes they are even nested or chained. This leads to such example:
async function anyFunction() {
try {
const result = await fetch("http://test.com");
} catch (e) {
// Some thing
}try {
const anotherresult = await someOtherAsync();
} catch (error) {
// some other error
}
}You are writing try/catch blocks everywhere to prevent errors that crash your app. Sometimes overly because:
- You want to standardize your errors management.
- And you don’t want to rely on external libraries' error format (or customize it).
You’ll concede: This is really boring, redundant and cumbersome.
But we’ll see below, we can do something thinner and very powerful with a small amount of code.
🙏🏻 Handling JavaScript errors in a cool manner
In every language handling exceptions is pretty common.
Some language - like PHP - are able to provide error types in catch instructions and are able to use multiple catch blocks. Some don’t and are a bit poor in the way to handle errors, like JavaScript.
You can quickly end in a situation where you are writing much code with much nesting or chaining, this can become very verbose and poorly maintainable.
async function anyFunction() {
try {
const result = await fetch("http://test.com"); try {
const another = await fetch("http://blabla.com");
} catch(anotherError) {
console.log(anotherError);
}
} catch (e) {
// Some other error handling
} try {
const anotherResult = await someOtherAsync();
} catch (errorFromAnotherResult) {
// Some other error
}
}💡Some language already have nice tools to deal with errors
Some languages like GOlang can return multiples values from return statements in functions. This allows us to return an error (or null) plus an object with the expected value from the Promise, everything in the same statement.
The following sample is a GO example of a network subscriber.
func Listen(host string, port uint16) (net.Listener, error) {
addr, addrErr := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:%d", host, port))
if addrErr != nil {
return nil, fmt.Errorf("Listen: %s", addrErr)
}
listener, listenError := net.ListenTCP("tcp", addr)
if listenError != nil {
return nil, fmt.Errorf("Listen: %s", listenError)
}
return listener, nil
}As you can see that this has “almost” the same pattern as a NodeJS callback, first arg is the value, the second arg is the error (in fact NodeJS callback have those in the inverted order… But you get the logic).
✅ You can also handle JavaScript errors using the same pattern
While JS doesn’t have multiple return statements, there is another way to implement the pattern we just saw above.
You can mimic this behavior by returning an array with 2 data, this also works in any language that supports arrays.
❓ Alright, can you show me the code now ?
To achieve this, we will need to wrap our async code with a small utility function. This function will format the resolving and rejecting arguments to an array of two elements. This wrapping function will be called here to but feel free to give it any name.
/**
* @param { Promise } promise
* @param { Object } improved - If you need to enhance the error.
* @return { Promise }
*/
export function to(promise, improved){
return promise
.then((data) => [null, data])
.catch((err) => {
if (improved) {
Object.assign(err, improved);
}
return [err]; // which is same as [err, undefined];
});
}This function is returning an array of two elements:
- On the then callback (if the Promise resolved): it returns
nulland thedataas there are no errors. - On the catch callback (if the Promise rejected): it returns the
errthat can be extended andundefinedas the second element as there is no data.
We can now take our original try/catch block and update it that way. What is done here is simply wrapping the someAsyncData Promise by our to function.
const [error, result] = await to(someAsyncData());if(error){
// log something and return ?
}const [error2, result2] = await to(someAsyncData2());if(error2){
// do something else
} else {
// Here we are sure that result2 is defined and a valid value
}Looks cool, isn’t it? That’s simple as that. If the promise resolves we return an array with null and the data, otherwise we return the error and undefined.
Now you can use that sequential like syntax to handle errors in addition to your traditional try/catch blocks.
Did you know that pattern? Are you already using it? Feel free to share your feelings here. In my concern, that was game-changer, even if it’s pretty simple and nothing new 😅. But be advised, this is not a drop-in replacement for a traditional way to deal with errors, just give it a try and build your own opinion.
Some libraries that implement kind of pattern are available here:





