DEV Community

Discussion on: Compact or Verbose Codestyle?

Collapse
 
qm3ster profile image
Mihail Malo

The short one returns the error instead of throwing it.

Collapse
 
f1lt3r profile image
F1LT3R • Edited

It's true, the behavior is slightly different. I will update this as it adds confusion. The throw will stop the code and unwind the execution stack until the error is caught.

Thread Thread
 
qm3ster profile image
Mihail Malo

The problem is that when not using a Result type this would actually be hard to use in JS.
You would have to check

const result = get('a','b')
if (result instanceof Error) throw result
doStuff(result)

Which isn't idiomatic.

Thread Thread
 
f1lt3r profile image
F1LT3R • Edited

Typically I would throw inside the get function here. I did not do this in the example because you can't throw inside the expression and I wanted to keep the logic identical between the two.

However, I do sometimes respond to the caller with something like:

{error: {
  msg,
  origin: new Error(msg)
}};

... then I handle that up the stack.

{
  setupView () {
    const something = await fetch ('something');

    if (something.error) {
      // handle error (throw or return  error depending on level of fatality)
      return something;
    }

    // Guarded logic
    updateView(something);
  }
}

I tend to bubble recoverable errors back up the stack.

Your comment about being idiomatic interested me. Can you elaborate? I might learn something valuable if you have the time to share an idiomatic example.

Thread Thread
 
qm3ster profile image
Mihail Malo

I don't think "Idiomatic JavaScript" is something to be particularly proud of, good patterns aren't the most popular here.

The approach with .error seems more conventional though, since this is similar to what you'd get in a JSON response on a remote error. (I only noticed the second example is literally await fetch after I finished writing this. haha)

Promises are already essentially Future<Result<T,E>>, with await really meaning (await promise).unwrap(), which IMHO is a big design flaw. This means that there isn't a built in Result type which we could use rich combinators with, while we can't properly express infalible futures like timeouts, which will never reject. And once await is added to the mix, we end up having to do the traditional, "idiomatic" try-catch around it.

What if you have a function that can throw, and on success returns a promise that might reject? Today you have to do:

try {
  const promise = beginThang()
} catch (err) {
  // handle E1
}
let /* wow, so glad this will be a mutable binding now */ result
try {
  result = await promise
} catch (err) {
  // handle E2
}
return makeUseOf(result)

While it could have been:

beginThang()
  .mapErr(err => {/*  handle E1  */}) // line optional, can just bubble error
  .andThen(async res => {
    (await res)
      .mapErr(err => {/*  handle E2  */}) // line optional, can just bubble error
      .map(makeUseOf)
  })

Note how we get a Result<Future<Result<T,E2>>,E1> from beginThang and ourselves return a Result<Future<Result<T2,E4>>,E3> (or Result<Future<Result<T2,E2>>,E1> if we just bubble errors), so immediate errors can be handles synchronously instead of wrapping our bad outcomes in a Promise.reject(err) so they wait until the next round of the event loop.

Thread Thread
 
qm3ster profile image
Mihail Malo

Another thing that is not used at all in "idiomatic JavaScript" is dependency injection.