DEV Community

Cover image for Say goodbye Trycatch Hell
Ivan Zaldivar
Ivan Zaldivar

Posted on • Edited on

Say goodbye Trycatch Hell

Hi everyone! It is possible that it is here for the post title, and I daresay you have had problems handling errors with async/await. In this post you will learn a trick to avoid trycatch hell but before, it is neccessary to know a little history.

History of Callback.

Once upon a time, developers had to deal with tasks that took a while, and the way we could check if the task was done was through callback.

Callbacks are nothing more than functions that are executed when a task has been completed, either erroneously or successfully. Until this moment, there is nothing bad. The problem arises when these callbacks in turn execute other functions, and so on. And that's when the macabre happens.

Animation Callback Hell

For this reason. Promise are born, as a solution to this problem.

Promises.

The promises are objects that represent the completion of a process, this can be a failure (reject) or a success (resolve). Also, lets add this beauty.

Comparation Promise vs Callback

Everything seemed magical, until...

Picture trycatch hell

Using async await makes the code more readable, it looks more beautiful, but it has a problem, is that if the promise fails, it will stop the flow of our system, so it is necessary to handle the errors.

But when handling these with trycatch we lose that readability, but don't worry, those are over now my dear friend.

How implemented.

First, we are going to simulate a whole. Let's do it.

We define some interfaces, and add test content.



interface Note {
  id: number;
  name: string;
}

interface Query {
  [key: string]: any;
}

const notes: Note[] = [
  {
    id: 1,
    name: "Megadeth",
  },
  {
    id: 2,
    name: "Korn",
  },
];



Enter fullscreen mode Exit fullscreen mode

We define some functions.



async function update(id: number, data: Omit<Note, "id">, options: Query): Promise<Note> {
  const index: number = notes.findIndex(n => n.id === id);
  if (index < 0) throw new Error("Note does not exist");

  const updated: Note = { id, ...data };
  notes.splice(index, 1, updated);
  return updated;
};

async function remove(id: number, options: Query): Promise<Note> {
  const index: number = notes.findIndex(n => n.id === id);
  if (index < 0) throw new Error("Note does not exist.");

  const note: Note = notes[index];
  notes.splice(index, 1);
  return note;
};



Enter fullscreen mode Exit fullscreen mode

We define our promise handler.



async function promHandler<T>(
  prom: Promise<T>
): Promise<[T | null, any]> {
  try {
    return [await prom, null];
  } catch (error) {
    return [null, error];
  }
}


Enter fullscreen mode Exit fullscreen mode

This function receives a promise as a parameter, then we execute the promise within the trycatch, in order to handle the errors, and we will return an array, in which the first index [0] will be the Response or Result and the second [1] the Error.

Note: You may see a T, this is necessary because we need to know the type of data at all times, they are called generics, if you need to know more, click on the following link: https://www.typescriptlang.org/docs/handbook/2/generics.html

Now, we only consume our handler.



  const [updated, err] = await promHandler(
    update(1, { name: "Mudvayne" }, {})
  );
  // updated -> {id: 1, name: "Mudvayne"}
  // err -> null

  const [removed, error] = await promHandler(remove(4, {}));
  // removed -> null
  // error -> Error "Does not exist."



Enter fullscreen mode Exit fullscreen mode

Now I ask you, does it look better?

Perfect, we already know how to avoid trycatch hell, but this only using promises, what about synchronous functions?

Handling synchronous functions.

We convert our previous functions to synchronous.



function update(id: number, data: Omit<Note, "id">, options: Query): Note {
  // ...
};

function remove(id: number, options: Query): Note {
  // ...
};


Enter fullscreen mode Exit fullscreen mode

We define our synchronous function handler.



function funcHandler<T extends any[], K>(
  func: (...args: T) => K,
  ...params: T
): [K | null, any] {
  try {
    return [func(...params), null];
  } catch (error) {
    return [null, error];
  }
}


Enter fullscreen mode Exit fullscreen mode

Explanation: This function is a bit different than the previous one, since in this one, we have to give it the function (without executing) and the parameters.

This function will have two generics, where T represents the parameters that the function receives, and K the value that it returns. Also, we make use of the spread syntax As we do not know the number of parameters that can reach us, we will make use of these 3 magic points (...) โœจ

And now, we carry out the previous process, first index, Result; second indicer, Error. And ready!

We carry out the operations.



  const [updated, err] = funcHandler(update, 1, { name: "Mudvayne" }, {});
  // updated -> {id: 1, name: "Mudvayne"}
  // err -> null

  const [removed, error] = funcHandler(remove, 6, {});
  // removed -> null
  // error -> Error "Does not exist."



Enter fullscreen mode Exit fullscreen mode

Great, we no longer have to struggle to make our code look more readable, and also, we reuse the handles.

You know, if you have something to contribute, a question, an improvement, you can contribute in the comments, and if it has been useful, leave your reaction, that makes me happy.

Follow me on social networks.

Latest comments (32)

Collapse
 
anshul_saini_fdaa7fb7ae1f profile image
Anshul Saini
Collapse
 
fkereki profile image
Federico Kereki

This is going in the direction of Maybe or Either monads.

Collapse
 
valery profile image
ะ’ะฐะปะตั€ะธะน ะšัƒะปะฐะบะพะฒ
  • First, you have not avoided the problem, because now You have to write some if (error) in the code, and it will be even worse.
  • Moreover, in fact, we most likely need a global error handler.
Collapse
 
cyrstron profile image
cyrstron

Not sure about golang, but you don't need this many try/catch in JS. Just handle an error only where you need to handle it. Otherwise let it go to a concerned parent.

Collapse
 
ivanhoe011 profile image
Ivan Dilber

Completely pointless refactor exercise IMHO, as now instead of try/catch-ing you need to check each time if a function returned an error or not.

When an exception is thrown it will jump out of all blocks, so there's no need to catch it at the same level. You can set just a single try/catch block on the parent (or even higher) scope to catch all of them.

Collapse
 
vnues profile image
vnues • Edited

This is too complicated, I usually do this

const to = (promise: any) => {
  if (!promise) {
    return new Promise((resolve, reject) => {
      reject(new Error('requires promises as the param'))
    }).catch((err: Error) => {
      return [err, null]
    })
  }
  return promise
    .then(function() {
      const arr = Array.from(arguments)
      return [null, ...arr]
    })
    .catch((err: Error) => {
      return [err, null]
    })
}

export const exec = async (sql: string) => {
  const [err, res] = await to(db.exec(sql))
  if (err) {
    throw new Error(err)
  }
  return res
}

// ...async function 
const [data,err] = await to(getUsers())
Enter fullscreen mode Exit fullscreen mode

Because you have to know, promsie comes with try/catch

Collapse
 
ricky11 profile image
Rishi U

you lost me at interfaces, not that i didn't understand, but just lost me.

Collapse
 
chuckytuh profile image
Joรฃo Gonรงalves

github.com/supermacro/neverthrow is something to look at, not only avoids the trycatch hell but it also provides a way to easily compose computations!

Collapse
 
ivanzm123 profile image
Ivan Zaldivar

I've been going through the package and it looks interesting. Thanks for your contribution.

Collapse
 
defite profile image
Nikita Makhov • Edited

Goodbye trycatch hell, hello Promise-async-await hell...

Collapse
 
r37r0m0d3l_77 profile image
Anton Trofimenko • Edited

Done years ago of.js.org

Collapse
 
laurentperroteau profile image
Laurent Perroteau

Another exemple : await-to-js that returns the error first to enforce it to be handled :

const [error, result] = await to(getList());

if (error) ...
Enter fullscreen mode Exit fullscreen mode
Collapse
 
hilleer profile image
Daniel Hillmann • Edited

Was about to point out it all seemed a bit like re-inventing the wheel, considering modules like this already exist.

I personally use and like await-to-js for many use cases.

Collapse
 
ivanzm123 profile image
Ivan Zaldivar

I like, but is not necessary why is Typescript being used (as long as the developer uses static typing)