Skip to content

Instantly share code, notes, and snippets.

@karlhorky
Last active October 19, 2022 23:43
Show Gist options
  • Save karlhorky/3593d8cd9779cf9313f9852c59260642 to your computer and use it in GitHub Desktop.
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 };
}
}
@karlhorky
Copy link
Author

i don't like this construct tbh as you loose the type inference at that point

What do you mean that you lose type inference? I don't see this behavior in the 'error' in result alternative...

@syed-ahmad
Copy link

So, when you do ('property' in object), you are just feeling lucky that it may or may not exist.

@syed-ahmad
Copy link

syed-ahmad commented May 6, 2021

Whereas using TS, the whole point is to leverage type inference and avoid compile time errors.

So, if the underlying contract changes to Promise<{ errorX: Error } | { data: T }>, TS won't show any warning/error for that line 'error' in result

@karlhorky
Copy link
Author

Using "prop" in obj is pretty common practice, check out the TypeScript Handbook section on Narrowing: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-in-operator-narrowing

@syed-ahmad
Copy link

Agreed, i got that wrong, should've played that on machine first before speculating 👍

I think I would still want the destructing to work for me so I can avoid those if blocks before I use that.

@syed-ahmad
Copy link

Using "prop" in obj is pretty common practice, check out the TypeScript Handbook section on Narrowing: https://www.typescriptlang.org/docs/handbook/2/narrowing.html#the-in-operator-narrowing

Sure, will do.

@syed-ahmad
Copy link

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
Copy link
Author

karlhorky commented May 6, 2021

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
Copy link

syed-ahmad commented May 6, 2021

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
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
Copy link

syed-ahmad commented May 6, 2021

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
Author

Thanks, I enjoyed the journey too! 🙌

@syed-ahmad
Copy link

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

@karlhorky
Copy link
Author

karlhorky commented Jun 29, 2021

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

@karlhorky
Copy link
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
Author

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

@karlhorky
Copy link
Author

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

microsoft/TypeScript#46266

@syed-ahmad
Copy link

Thanks @karlhorky

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