arthurfiorette/proposal-safe-assignment-operator

re: Why Not data First?

smeijer opened this issue · 11 comments

Why Not data First?
In Go, the convention is to place the data variable first, and you might wonder why we don't follow the same approach in JavaScript. In Go, this is the standard way to call a function. However, in JavaScript, we already have the option to use const data = fn() and choose to ignore the error, which is precisely the issue we are trying to address.

I believe there's another reason to stick to error first in js, and that's for language consistency in Nodejs, the most used JS server runtime. When using callback functions, the convention is to use an error first pattern. Like node's new glob util:

import { glob } from 'node:fs';

glob('**/*.js', (err, matches) => {
  if (err) throw err;
  console.log(matches);
});

Mapping that to the following feels almost natural.

const [err, matches] ?= glob('**/.js');
if (err) throw err;
console.log(matches);

I think the error-first callback pattern is popular enough, even in libraries, to support this case of error first, and it might be worth mentioning in the proposal.

I believe there's another reason to stick to error first in js, and that's for language consistency in Nodejs, the most used JS server runtime.

Error first for callbacks makes sense because you always want to check the error, and parameters after the first one are not guaranteed.

Error first in the proposal is very odd, in particular because the pattern it is emulating doesn't do error first. So it will be confusing for people working in multiple languages. Thus, it will be a source of friction. But it's also weird without considering any other languages:

// As proposed:
const [error, data] = await something()
// But if we insist on being silly and ignoring the error:
const [ , data] = await something()

// Switching to error second:
const [data, error] = await something()
// And that silly case again:
const [data] = await something()

In other words, as proposed, I can envision code bases littered with ugle [, data] all over the place.

Having [data, error], or [data] we can easily SUPRESS the error which is different than just ignoring the error.

Ignoring an error:

const val = await fn()
// this throws if fn rejects

Suppressing the error:

const [, data] = await fn()
const val = fn().catch(() => null)
// this simply does nothing and makes val undefined if fn throws.

From the What This Proposal Does Not Aim to Solve section of this proposal:

Automatic Error Handling: While this proposal facilitates error handling, it does not automatically handle errors for you. You will still need to write the necessary code to manage errors; the proposal simply aims to make this process easier and more consistent.

Completely suppressing errors is totally different from just ignoring the possibility of a rejection and the handle needed in case it rejects.

// this gets data in a simpler syntax and throws if an error happens
const data = fn()

// This is WORSE because it simply makes the error disappear.
const [data] ?= fn()

And forgetting to add a , error in the tuple destructuring is something we might mistakenly do. This proposal does not want to increase the number of silly mistakes because of bad syntax.


Besides that, it also follows an already present syntax in callbacks, where the error parameter is always the first one.

Besides that, it also follows an already present syntax in callbacks, where the error parameter is always the first one.

I addressed that at the top of my comment.

[,data] is not as legible as [data].

Besides that, it also follows an already present syntax in callbacks, where the error parameter is always the first one.

I addressed that at the top of my comment.

[,data] is not as legible as [data].

why not legible?

Besides that, it also follows an already present syntax in callbacks, where the error parameter is always the first one.

I addressed that at the top of my comment.

[,data] is not as legible as [data].

This, but isn't it better to make ignoring the error harder? If you deliberately want to ignore it, do not use this operator or maybe not babysit and have data as first? I had the same thought too, somehow seemed clearer and could just follow Go to make jumping languages easier. Why make superficial changes without much point? Just keep it same... Eslint can have a rule to warn about not handling error or otherwise it's a mistake to use this operator. No point to reason adjusting something avoids mistakes.