-
-
Notifications
You must be signed in to change notification settings - Fork 23
Description
As you might've seen in some issues around here. There's a discussion about the usage of [tuples]
or {objects}
as the result for this operator, as well as falsy
errors, like throw undefined
or more common cases like throw functionThatMightReturnUndefinedOrAnError()
.
Here's things that this proposal will NOT solve for you.
-
This proposal does not aims to create a
Result<T>
like object class with unary operations like.unwrap()
,.mapError(fn)
and/or.orElse(data)
. This can be easily achievable by current JS code and I'm pretty sure there's tons of libraries like this one out there. -
This proposal will not change the behavior of any function or its return type. The safe assignment operator (or future try expression as of Discussion: Preferred operator/keyword for safe assignment #4) is to improve the caller experience and not the callee.
Return type syntaxes
I'll be using the chosen syntax at #4 for these examples.
Tuple:
Tuples are easier to destructure and follows the concept of error first destructuring.
Simple use case:
const [error, data] = try fn();
if (error) {
console.error(error);
return;
}
console.log(data);
Complex use case:
const [reqErr, response] = try await fetch('url')
if (reqErr) {
console.error(reqErr);
return;
}
const [parseErr, json] = try await response.json();
if (parseErr) {
console.error(parseErr);
return;
}
const [validateErr, validatedData] = try validate(json);
if (validateErr) {
console.error(validateErr);
return;
}
return validatedData;
Objects:
Simple use case:
const { error, data } = try fn();
if (error) {
console.error(error);
return;
}
console.log(data);
Complex use case:
const { error: reqErr, data: response } = try await fetch('url')
if (reqErr) {
console.error(reqErr);
return;
}
const maybeJson = try await response.json();
if (maybeJson.error) {
console.error(maybeJson.error);
return;
}
const { data: validatedData, error: validatedError } = try validate(maybeJson.data);
if (validatedError ) {
console.error(validatedError );
return;
}
return validatedData;
Both of them
An Object with error
, data
property and Symbol.iterator
is also a valid solution.
Later we would need to improve the TS typings for that Symbol.iterator result to not mix error | data
types on both destructure values, but since TS already did that for tuples ([1, 2, 3]
) it won't be a problem.
Falsy values
Ideally, we should have some sort of wrapper against falsy
errors, as in the end having falsy values wrapped are much helpful in such problems.
Besides intended use cases, no one expects to face a null
inside a catch(error)
block or neither will know how to handle that correctly.
Since errors are now treated as values, in both return types, we should have a way to differentiate them.
We have two main approaches:
helper property
Just like on Promise.allSettled
's status
property, we could have a .ok
or something to tell the user that if the operation failed or not.
// obviously ok, could have as syntax like `fail` or a `status === 'err' || 'ok'`
const [ok, fnError, fnData] = try fn()
const { ok, error: fnError, data: fnData } = try fn()
if (!ok) {
console.log(fnError)
}
const maybeResult = try fn()
if (!maybeResult.ok) {
console.log(maybeResult.error)
}
Error wrapper
Wrapping the falsy value (0
, ''
, null
, ...) into a NullableError
class or something could arguably be presented as a good feature of this proposal as well, where we reduce the amount or burden that these errors would mean into a more useful value.
For example, an application crashing with undefined
and nothing more in the terminal is way worse than having an error like the following is much more useful in all meanings:
NullableError: Caught false error value
at REPL20:1:1
at ContextifyScript.runInThisContext (node:vm:136:12)
at REPLServer.defaultEval (node:repl:598:22)
at bound (node:domain:432:15)
at REPLServer.runBound [as eval] (node:domain:443:12)
at REPLServer.onLine (node:repl:927:10)
at REPLServer.emit (node:events:531:35)
at REPLServer.emit (node:domain:488:12)
at [_onLine] [as _onLine] (node:internal/readline/interface:417:12)
at [_line] [as _line] (node:internal/readline/interface:888:18) {
value: ''
}