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 null and the data as there are no errors.

(if the Promise resolved): it returns and the as there are no errors. On the catch callback (if the Promise rejected): it returns the err that can be extended and undefined as 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.