@claudiameadows My two cents on why const [error, result] = try ...
is better:
Using {caught, value}
makes it easy for people to write bad code because you can't know what value
is really going to be:
const {value: user} = try getUser()
if (user) {
console.log('user name is ' + user.name)
}
The code looks it could be sensible for someone who hasn't carefully read the docs, or just forgets. And it will work most of the time, so it's very possible it'll make it through a code review and a test suite. But when an error is thrown, we're going to print user name is undefined
instead of doing something sensible.
The destructuring approach also makes it more awkward when you don't care about the error. Here's an example where I want to load the eslint module, and get the ESLint
class from it, if it exists (it will be undefined for old versions of eslint).
With the tuple approach:
const [_err, ESLint] = try require('eslint').ESLint
if (ESLint) {
const linter = new ESLint()
console.log(linter.lintText(...))
}
With a destructured object:
const {caught, value} = try require('eslint').ESLint
if (!caught) {
const ESLint = value.ESLint
if (ESLint) {
const linter = new ESLint()
console.log(linter.lintText(...))
}
}
In this case, I don't care about the error, I just care if I can get a value for ESLint
, but the object form forces me to deal with the caught
variable.
As for the throw undefined
scenario, I agree with @coderaiser that it's an edge-case and it should be treated as such.
The try getUser()
syntax could automatically wrap throw undefined
with throw Object.assign(throw Error('falsy value thrown!'), { value: undefined })
. If people don't like that and want to stick with their weird falsy errors, they can just not use the syntax. They shouldn't drag the rest of us who want nothing to do with falsy errors down with them.
It's worth noting, libraries like fp-ts manage this by not using an overloaded value
property on their Either
type, they use { _tag: 'Left', left: {...} }
and { _tag: 'Right', right: {...} }
.