Al desarrollar un microservicio simple en hapijs, me di cuenta que al siempre utilizar async/await casi era obligado en ciertos escenarios utilizar try/catch (Definido así desde arquitectura inicial del proyecto), lo cual no me tenía del todo convencido y recien empezando con Go me di cuenta que no manejan este tipo de excepciones por los siguientes principios.

  1. Usar excepciones para errores esperados es una complejidad básicamente innecesaria.
  2. Además, mucha gente ha notado que mover el manejo de errores lejos del origen del error hace que las personas no piensen en las rutas de error.
  3. Cuando tienes que pensar qué hacer con el error en cada llamada, terminas pensando en las rutas de error, lo que conduce a un código de mayor calidad.

Pongamos un ejemplo

export async function get(req, res) {
  try {
    const users = await Users.find();

    if (users && users.length > 0) {
      // hacer algo de negocio
    }

    return users;
  } catch (err) {
    throw boom.boomify(err);
  }
}

Este es el código inicial y me pareció buena idea realizar un manejador para los casos donde algo falle e intentar programar algo más modular.

Un handler

async function handle(promise) {
  const response = {
    data: undefined,
    error: undefined,
  };

  if (!promise || typeof promise !== "function") {
    response.error = "Not promise";

    return response;
  }

  await promise()
    .then((data) => {
      response.data = data;
    })
    .catch((error) => {
      response.error = error;
    });

  return response;
}

Es sencillo, definimos un objeto con dos atributos: data y error.

const response = {
  data: undefined,
  error: undefined,
};

Para casos donde no mandemos el parámetro de promesa en la function, se podría validar así:

if (!promise || typeof promise !== "function") {
  response.error = "Not promise";

  return response;
}

Por último ejecutamos el promise y retornamos:

await promise()
  .then((data) => {
    response.data = data;
  })
  .catch((error) => {
    response.error = error;
  });

return response;

El ejemplo práctico podría funcionar de la siguiente manera:

export async function get(req, res) {
  const users = await handle(() => Users.find());

  if (users.error) {
    // aquí ocurrió un error y se puede tomar decisión
    throw boom.boomify(users.error);
  }

  if (users.data && users.data.length > 0) {
    // hacer algo de negocio
  }

  return users.data;
}

Como lo vemos ya no dependemos de try/catch, es un código que en definitiva puede aportar a la legibilidad y reducir los posibles bugs al tener un control más preciso sobre los errores.

¿Qué mejorarías, o te quedarías con try/catch para siempre?