DEV Community

Discussion on: Say goodbye Trycatch Hell

Collapse
 
stradivario profile image
Kristiqn Tachev • Edited

You need to catch exceptions on the top level of the function and try not to use the try catch scope until you need to re throw something specific.

With the following approach we are hiding unhandled rejections by providing status code 500 (Database errors for example) and only the correct thrown exceptions will be shown as a result of the requester.

import { randomBytes } from "crypto";

const generateUID = () => randomBytes(8).toString("hex");

/* @HttpCodes
 * Partial list of http codes
 */
export enum HttpCodes {
  Ok = 200,
  Redirect = 302,
  BadRequest = 400,
  Unauthorized = 401,
  PaymentRequired = 402,
  Forbidden = 403,
  NotFound = 404,
  MethodNotAllowed = 405,
  NotAcceptable = 406,
  ProxyAuthenticationRequired = 407,
  RequestTimeout = 408,
  Conflict = 409,
  Gone = 410,
  LengthRequired = 411,
  PreconditionFailed = 412,
  PayloadTooLarge = 413,
  URITooLong = 414,
  UnsupportedMediaType = 415,
  RangeNotSatisfiable = 416,
  ExpectationFailed = 417,
  ImATeapot = 418,
  MisdirectedRequest = 421,
  UnprocessableEntity = 422,
  Locked = 423,
  FailedDependency = 424,
  UnorderedCollection = 425,
  UpgradeRequired = 426,
  PreconditionRequired = 428,
  TooManyRequests = 429,
  RequestHeaderFieldsTooLarge = 431,
  UnavailableForLegalReasons = 451,
  InternalServerError = 500,
  NotImplemented = 501,
  BadGateway = 502,
  ServiceUnavailable = 503,
  GatewayTimeout = 504,
  HTTPVersionNotSupported = 505,
  VariantAlsoNegotiates = 506,
  InsufficientStorage = 507,
  LoopDetected = 508,
  BandwidthLimitExceeded = 509,
  NotExtended = 510,
  NetworkAuthenticationRequired = 511,
}

/* @HttpError
 * Main generic http status error handler
 */
export class HttpError extends Error {
  public reason?: string;
  public isCustomError? = true;
  constructor(
    public readonly message: string | never,
    public readonly statusCode: HttpCodes,
    public readonly details?: object,
  ) {
    super(message);
    this.name = HttpCodes[statusCode];
    this.message = message;

    /* When message is different than status text we have a custom message reason */
    if (HttpCodes[statusCode] !== message) {
      this.reason = message;
    }
  }
}

/* @HttpCodeEnumType
 * Generic enumerable for strict checking of all http codes
 * example: throw HttpErrors.Unauthorized()
 */
export type HttpCodeEnumType<T, K> = {
  [key in keyof typeof HttpCodes]: (
    message?: T,
    details?: object | undefined,
  ) => K;
};

/* @HttpErrors
 * Record of httpErrors
 * example: throw HttpErrors.Unauthorized()
 */
export const HttpErrors: HttpCodeEnumType<string, HttpError> = Object.values(
  HttpCodes,
)
  .filter(k => typeof HttpCodes[k as never] === 'number')
  .reduce((codes, code) => ({
      ...codes,
      [code]: (message = code as never, details: never) =>
        new HttpError(message, HttpCodes[code as never] as never, details),
    }), {} as HttpCodeEnumType<string, HttpError>);


async function myAsyncFunctionNext() {
  return 1;
}
async function myAsyncFunctionNext2() {
  return 2;
}
async function myAsyncFunctionNext3() {
  throw HttpErrors.BadGateway()
  return 3;
}

async function myAsyncFunctionNext4() {
  throw new Error('unauthorized')
}


async function myAsyncFunction() {
  await myAsyncFunctionNext();
  await myAsyncFunctionNext2();
  await myAsyncFunctionNext3();
  // Only try catch if you really want to change the error from specific message thrown
  try {
    await myAsyncFunctionNext4()
  } catch (e) {
    if (e.message === 'unauthorized') {
      throw HttpErrors.Unauthorized()
    }
    throw e;
  }
  return 'successs';
}

export async function handler() {
  try {
    return {
      statusCode: 200,
      body: await myAsyncFunction()
    }
  } catch (e) {
    if (e.isCustomError) {
      return {
        statusCode: e.statusCode,
        body: JSON.stringify(e),
      };
    }

    const uuid = [generateUID(), "-", generateUID(), "-", generateUID()].join(
      ""
    );
    console.error(`[${uuid}]:`, e);
    return {
      statusCode: 500,
      body: JSON.stringify({
        name: "InternalServerError",
        details: { uuid },
      }),
    };
  }
}

Enter fullscreen mode Exit fullscreen mode