Skip to content

Tuples/Objects and Falsy errors. #30

@arthurfiorette

Description

@arthurfiorette

Additional post on twitter

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: ''
}

Metadata

Metadata

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions