DEV Community

Discussion on: Why I Don't Use Async Await

 
jesterxl profile image
Jesse Warden
  1. lol, that's a rabbit hole. If you're cool with imperative or OOP, it can help a lot, but most imperative/OOP developers don't return Errors from functions, they throw them. You can use never which is helpful in that it will throw something, but TypeScript doesn't have throwable like Java. So the compiler can help you a little bit and say "Oh man, this function doesn't return a value, you should probably wrap this with a try catch". And yeah, if you extend Error, then yeah, you'll get way more help from the compiler returning different types of Exceptions.

... but that doesn't really make sense. If you're willing to do the whole Exception dance, then you are cool with try/catch, and should just either only use it in exceptional circumstances like the OOP and imperative kids do.

If, however, you want to join the church of pure functions, then it's better to return a Result. The types in TypeScript are a bit tough, but I've seen a few type and interface implementations that do help. For example, an Array in TypeScript will use like Array<string> or [string] style syntax. That's helpful for Result because some of the libraries will do Result<boolean> which is kind of cool.

Either way, we're kind of at the limits of JavaScript here; deviating from throw and try/catch is not normal JavaScript. Doing Functional Programming in a non-functional language is hard, and you start getting into these weird situations where you "think of returning an Error from a function instead of throwing it" or "wrapping a failure in an Object and returning it". If you're a functional programmer, it all makes sense, AND when you're in a different land, you try to make it work. Not everything makes sense because the langauge doesn't make sense; it allows things that are "wrong"; wrong being against FP rules. Exceptions are in this weird middle ground; some people think exceptions in Haskell are ok; others don't. Either way, Result is kind of normalized in that world, so that's what we do.

Using types helps a TON once you start composing functions together, though. Even just regular promises using variadic tuples.

  1. Well... sort of. I think it's hugely worth spending the time to help. Like fetch is great, but you get this stack trace, and it's not always obvious why it failed. So spending the effort to be a bit more obvious will help you a ton when things start blowing up.

You still have these problems in functional languages, though. Like if you try to JSON.parse safely, with types, a response from the server and it fails, by default you might get "Failed to parse JSON." That is completely worthless and frustrating. Better to be "Attempted to parse a string firstName, and found a field called firstName, but it was null". NOW we're getting somewhere. So you can create crap errors or good errors; best to spend the effort. It's just in functional programming, it's wayyyyy more obvious where it came from: the function vs. "somewhere in the function". Although, when you start composing them together, that's not really helpful if you have a 30 function Promise chain, "Where in this chain did this error come from?".

However, I wouldn't say more effort, just that its' SUPER easy to screw up in imperative land. For example, this is ok in JavaScript (I'm not sure about TypeScript):

if(thing) {
  return true
}
return
Enter fullscreen mode Exit fullscreen mode

In a typed language, they'd prefer something like:

if(thing) {
  return true
}
return false
Enter fullscreen mode Exit fullscreen mode

However, you could later go:

if(thing) {
  return true
}
if(otherThing){
  return "something"
}
return false
Enter fullscreen mode Exit fullscreen mode

That kind of problem would never surface in something like Elm or ReScript; you're not allowed to omit the else, nor miss some of the elses, and the types must be the same. Imperative style code like that can get you into trouble. If you don't have those types of scenarios, then it makes it a lot easier to write more specific errors. If you go play in Elm or Haskell, you'll see what I mean. ReScript and F# are a bit more flexible, and you can run into the same types of problems where you have large functions that can do too many things.