Should we disallow extra arguments in newer, built-in JavaScript functions?

Should we start throwing an error, whenever the end-user supplies extra arguments to a built-in function? Such a change will only affect new functions, existing functions would remain unchanged.

For example, looking at the array grouping proposal, instead of array.group(x => x % 2 === 0, null) being valid, an error would be thrown.

The reason for this, is to carve out a safe space for future expansion. For example, if we want to later come out with a proposal that causes array.group() to take a second optional options bag to control some behavior, we can safely add such a behavior in, without worrying too much about "breaking the web".

The reason end-users may supply extra, ignored arguments today, is when they use higher-order functions. As an example, it's a somewhat common pattern to see something like .map(Number), to convert an array of strings to an array of numbers. It works, but it only works because the Number function takes exactly one argument and ignores the rest. It would be a breaking change to update the Number() function to take a second, optional numeric argument, say, a radix (the same way parseInt() does), to control its behavior, since that would cause anyone's code that has .map(Number) in it to break (the index argument from .map() will automatically and incorrectly be passed in as the optional radix).

I know there's been [previous discussion(Make readonly TypedArray - #21 by ljharb), where it was confirmed that, in general, we should avoid using built-ins with higher-order functions like this .map(Number), and instead use .bind() or an arrow function, to limit the number of arguments being passed in, like this .map(x => Number(x)). Basically, if we decide to throw an error if too many arguments are given to built-ins, we'd be enforcing this kind of pattern.

The downside is that the standard library, thus far, has not followed such a pattern. While I can't see any tactical reason why it would be bad to make the switch now, there is an inconsistency reason, where it just makes the language feel more incoherent, adding to an overall ickiness feeling about the language.

2 Likes

As a data point — even though the conditions for the tradeoff are likely quite different for ECMAScript — GNOME's JS platform logs a warning when excess arguments are passed to any API, based on the rationale that an excess argument is more likely to be an accident than use as a callback, and for the callback case there is an easy workaround in the form of an arrow function as you mentioned.

GNOME's JS platform APIs are all bindings to C APIs. So an excess argument is actually quite a common mistake if you're familiar with the C version of an API, since argument pairs in C like uint8_t* bytes, size_t bytes_len get translated into one Uint8Array argument in JS.

Even then, at the time the problem was identified, there was enough existing code relying on excess arguments not throwing, that we chose to log a warning instead of throw.

4 Likes

One easy (but undesirable) way to preserve the consistency of the lang, is to add another "use-like" directive. Something like "use params";. Or something more readable, like 'forbid excess_args';

However, the point is to make it easier for tc39 to add new optional parameters to existing functions. If users can choose to add extra arguments anyways (by not using the directive), then we still have the same potential issue where allowing new optional parameters could break existing code.

True. I forgot to add that my solution is not well-thought, that's why it's "easy" to implement, but hard to maintain on the long run (too much technical debt)