/try

Don't let the Try Catch Tower of Terror destroy your beautiful one liners.

Primary LanguageTypeScriptMIT LicenseMIT

Try™

Don't let the Try Catch Tower of Terror destroy your beautiful one liners.

Usage

npm install @bdsqqq/try
import { trytm } from "@bdsqqq/try";

const mockPromise = () => {
  return new Promise<string>((resolve, _) => {
    setTimeout(() => {
      resolve("hello from promise");
    }, 1000);
  });
};

const mockPromiseThatFails = () => {
  return new Promise<string>((_, reject) => {
    setTimeout(() => {
      reject(new Error("hello from promise"));
    }, 1000);
  });
};

const [data, error] = await trytm(mockPromise());
const [data2, error2] = await trytm(mockPromiseThatFails());

Why does this exist?

Async await feels like heaven because it avoids the callback hell or Pyramid of Doom by writing asyncronous code in a line by line format:

function hell() {
   step1((a) => {
      step2((b) => {
         step3((c) => {
            // ...
         })
      })
   })
}

async function heaven(){
   const a = await step1();
   const b = await step2(a);
   const c = await step3(b);
   // ...
}

Until error handling comes into play... Because then you end up with the Try-Catch Tower of Terror, where your beautiful one-liners magically expand to at least 5 lines of code:

async function Terror(){
   let a;
   let b;
   let c;

   try {
      a = await step1();
   } catch (error) {
      handle(error);
   }


   try {
      b = await step2(a);
   } catch (error) {
      handle(error);
   }


   try {
      c = await step3(b);
   } catch (error) {
      handle(error);
   }

   // ...
}

An easy solution would be to append the .catch() method to the end of each promise:

async function easy(){
   const a = await step1().catch(err => handle(err));
   const b = await step2(a).catch(err => handle(err));
   const c = await step3(b).catch(err => handle(err));
   // ...
}

This approach solves the issue but can get a bit repetitive, another approach is to create a function that implements one Try Catch to replace all the others:

import { trytm } from "@bdsqqq/try"

async function awesome() {
   const [aData, aError] = await trytm(step1());
   if(aError) // ...

   const [bData, bError] = await trytm(step2(aData));
   if(bError) // ...

   const [cData, cError] = await trytm(step3(bData));
   if(cError) // ...

   // ...
}

Why does this REALLY exist?

I watched a fireship short and ended up in a rabbit hole to learn how to publish a NPM package. This is still an interesting pattern to use in your codebase but might be best copy pasted instead of being a dependency.

I'll leave the source code here so you don't have to look for the one .ts file in the /src folder:

export const trytm = async <T>(
   promise: Promise<T>,
): Promise<[T, null] | [null, Error]> => {
   try {
      const data = await promise;
      return [data, null];
   } catch (throwable) {
      if (throwable instanceof Error) return [null, throwable];

      throw throwable;
   }
};

Attributions

This code is blatantly stolen from a fireship youtube short, with minor additions to make data infer its typing from the promise passed as an argument.