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.
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:newError(msg)}};
... then I handle that up the stack.
{setupView(){constsomething=awaitfetch('something');if(something.error){// handle error (throw or return error depending on level of fatality)returnsomething;}// Guarded logicupdateView(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.
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{constpromise=beginThang()}catch(err){// handle E1}let/* wow, so glad this will be a mutable binding now */resulttry{result=awaitpromise}catch(err){// handle E2}returnmakeUseOf(result)
While it could have been:
beginThang().mapErr(err=>{/* handle E1 */})// line optional, can just bubble error.andThen(asyncres=>{(awaitres).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.
The short one returns the error instead of throwing it.
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.
The problem is that when not using a
Result
type this would actually be hard to use in JS.You would have to check
Which isn't idiomatic.
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:
... then I handle that up the stack.
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.
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 literallyawait fetch
after I finished writing this. haha)Promises are already essentially
Future<Result<T,E>>
, withawait
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:
While it could have been:
Note how we get a
Result<Future<Result<T,E2>>,E1>
frombeginThang
and ourselves return aResult<Future<Result<T2,E4>>,E3>
(orResult<Future<Result<T2,E2>>,E1>
if we just bubble errors), so immediate errors can be handles synchronously instead of wrapping our bad outcomes in aPromise.reject(err)
so they wait until the next round of the event loop.Another thing that is not used at all in "idiomatic JavaScript" is dependency injection.