Skip to content

Instantly share code, notes, and snippets.

@karlhorky
Last active October 19, 2022 23:43
Show Gist options
  • Select an option

  • Save karlhorky/3593d8cd9779cf9313f9852c59260642 to your computer and use it in GitHub Desktop.

Select an option

Save karlhorky/3593d8cd9779cf9313f9852c59260642 to your computer and use it in GitHub Desktop.
Try-catch helper for promises and async/await
export default async function tryCatch<Data>(
promise: Promise<Data>,
): Promise<{ error: Error } | { data: Data }> {
try {
return { data: await promise };
} catch (error) {
return { error };
}
}
@syed-ahmad

Copy link
Copy Markdown

Here you go, destructuring works like a charm now 🎉
Try in TS Playground

export default async function tryCatch<T>(
  promise: Promise<T>
): Promise<{ error?: Error; data?: T }> {
  try {
    return { data: await promise };
  } catch (error) {
    return { error };
  }
}

(async function a() {
  const {error, data} = await tryCatch(fetch(""));

 console.log(error, data);
})();

@karlhorky

karlhorky commented May 6, 2021

Copy link
Copy Markdown
Author

Nice, good job! 🎉

The only reason that I tend to avoid this pattern is because then you cannot properly narrow the type of data.

As soon as you destructure, TypeScript "forgets" the association between error and data, meaning that you'll need to check whether data exists every time (see the | undefined part?):

Screen Shot 2021-05-06 at 18 20 05

@syed-ahmad

syed-ahmad commented May 6, 2021

Copy link
Copy Markdown

Drat! you are spot on.

I'm just debating with myself if I can live with the following? What do you reckon @karlhorky?

const fn = async () => {
  const promise = fetch(`https://cdn2.thecatapi.com/images/lm.jpg`);

  const { error, data } = await tryCatch(promise);

  if (error) return handleError(error);

  const stuff = await data?.json();

  doSomethingWith(stuff);
};

@karlhorky

Copy link
Copy Markdown
Author

Yeah, I started with this pattern too, using the optional chaining. I think it's not so bad for one usage!

But if you've got a long file where you're using data in a lot of places, then it may be worth it to consider avoiding the destructuring in the first step. That's where I ended up with most of my code.

@syed-ahmad

syed-ahmad commented May 6, 2021

Copy link
Copy Markdown

I'm going to go with optional chaining as I tend to keep my functions small, let's see how it goes.

Great chatting to you.

@karlhorky

Copy link
Copy Markdown
Author

Thanks, I enjoyed the journey too! 🙌

@syed-ahmad

Copy link
Copy Markdown

Hi @karlhorky, is it okay if I create an npm package for that with tests?

@karlhorky

Copy link
Copy Markdown
Author

@karlhorky

karlhorky commented Jun 29, 2021

Copy link
Copy Markdown
Author

TS 4.4 may no longer "forget" the type information after destructuring: https://twitter.com/sebastienlorber/status/1409543348461965314?s=19

@karlhorky

Copy link
Copy Markdown
Author

Ok, this is true for a single variable, but it doesn't apply for multiple variables being destructured: https://stackoverflow.com/a/59786171/1268612

@karlhorky

Copy link
Copy Markdown
Author

It looks like this is actually a separate feature request called "Correlated Unions" by jcalz here: microsoft/TypeScript#30581

@karlhorky

Copy link
Copy Markdown
Author

Seems like this may be in TypeScript 4.6, since this PR got merged:

microsoft/TypeScript#46266

@syed-ahmad

Copy link
Copy Markdown

Thanks @karlhorky

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment