No more Try/Catch: a better way to handle errors in TypeScript

Noah - Nov 4 - - Dev Community

Hello everyone.

Have you ever felt that Try/Catch is a bit inconvenient when developing an application in TypeScript?

I found an intersting video on YouTube that describes how to handle errors in TypeScript in a simple way.
I'm sharing insights from the video as a review.

If you have any other good alternatives to try/catch, I would love to hear them!

Defining the getUser function for error handling

First of all, I defined a simple getUser function to illustrate error handling.
It returns a new user with the given id.

const wait = (duration: number) => {
  return new Promise((resolve) => {
    setTimeout(resolve, duration);
  });
};

const getUser = async (id: number) => {
  await wait(1000);

  if (id === 2) {
    throw new Error("404 - User does not exist");
  }

  return { id, name: "Noah" };
};

const user = await getUser(1);

console.log(user); // { id: 1, name: "Noah" }
Enter fullscreen mode Exit fullscreen mode

Error Handling using try/catch

Rewriting the previous code using try/catch, it looks like this.

const wait = (duration: number) => {
  ...
};

const getUser = async (id: number) => {
 ...
};

try {
  const user = await getUser(1);
  console.log(user); // { id: 1, name: "Noah" }
} catch (error) {
  console.log("There was an error");
}
Enter fullscreen mode Exit fullscreen mode

Problem with try/catch ①: It handles every error that occurs within the try block

The code below is not ideal.
Even though it's just a typo, "There was an error" is displayed in the console. I only want to handle errors that occur specifically in getUser within this try/catch block.

const wait = (duration: number) => {
  ...
};

const getUser = async (id: number) => {
 ...
};

try {
  const user = await getUser(1);
  console.log(usr);               // ← There was an error
  // ... (a lot of code)
} catch (error) {
  console.log("There was an error");
}
Enter fullscreen mode Exit fullscreen mode

Problem with try/catch ②: The Pitfall of Using let

Okay then, let's try to solve it using let.

const wait = (duration: number) => {
  ...
};

const getUser = async (id: number) => {
 ...
};

let user;

try {
  user = await getUser(1);
  // ... (a lot of code)
} catch (error) {
  console.log("There was an error");
}

console.log(usr); // ← ReferenceError: Can't find variable: usr
Enter fullscreen mode Exit fullscreen mode

I got an actual error from the typo, but this code is still not ideal because I can accidentally redefine the user object, like below.

const wait = (duration: number) => {
  ...
};

const getUser = async (id: number) => {
 ...
};

let user;

try {
  user = await getUser(1);
  // ... (a lot of code)
} catch (error) {
  console.log("There was an error");
}

user = 1 // ← ❌ It might lead to a bug.
Enter fullscreen mode Exit fullscreen mode

Solution

It's much simpler and more readable, don't you think?
Furthermore, the user variable is immutable and won't lead to unexpected errors.

const wait = (duration: number) => {
  ...
};

const getUser = async (id: number) => {
 ...
};

const catchError = async <T>(promise: Promise<T>): Promise<[undefined, T] | [Error]> => {
  return promise
    .then((data) => {
      return [undefined, data] as [undefined, T];
    })
    .catch((error) => {
      return [error];
    });
};

const [error, user] = await catchError(getUser(1));

if (error) {
  console.log(error);
}

console.log(user);
Enter fullscreen mode Exit fullscreen mode

Please take a look at the video, which we have referenced. He explains it very carefully.

I have never actually used this pattern in actual work.I just wanted to hear your opinion on how practical it is.Because it was discussed in his Yotube comments and I wanted to know the answer. I’ll be exploring best practices based on the comments.👍

Happy Coding☀️

. . . . .