5

What is a common way to implement a JavaScript function whose middle argument(s) are optional, but the last argument is required? An idiomatic ES2015 solution is preferred.

E.g., I commonly see functions that can be invoked in multiple ways, like this:

func(firstArg, callback);
func(firstArg, options, callback);

So what actually happens is that the number of arguments affects the interpretation of each argument.

I know I can handle this by checking the length of the arguments, something like this:

//     func(firstArg, options, callback);
// or: func(firstArg, callback);
function (firstArg, ...args) {
    let options = {};
    if (args.length > 1) {
        options = args.shift();
    }
    let callback = args[0];
    // And do stuff ...
}

But it seems clunky. Is there a better way to do this?

brianmearns
  • 9,581
  • 10
  • 52
  • 79
  • Pass in an object instead. – Andy Apr 06 '16 at 14:24
  • The `options` argument is the object. – brianmearns Apr 06 '16 at 14:28
  • 1
    Most languages enforce an *only optional arguments may follow an optional argument* rule for variadic functions to avoid this confusion/awkwardness in the first place. – Alex K. Apr 06 '16 at 14:34
  • @AlexK. Sure, but the node community breaks a lot of rules =). This isn't limited to JavaScript, though; a lot of languages have something like the python `range` function which looks something like `range([start,] stop)`. – brianmearns Apr 06 '16 at 15:03
  • 2
    An idiomatic ES6 solution is to return a promise instead of taking a callback. And optional arguments always come last. – Bergi Apr 06 '16 at 15:42
  • Then perhaps what I really want is an idiomatic _node_ solution, using ES6 syntax. – brianmearns Apr 06 '16 at 15:43
  • I think this is related to TypeScript's function overloading feature. – blackr1234 Nov 28 '19 at 06:00

2 Answers2

2

Use ...args and pop() the callback from the array:

function thing(...args) {
  var cb = args.pop();
  //... args is now [1, obj]
  cb(args[0]);
}

thing(1, { a: 1 }, function (data) {
  console.log(data); // 1
});
Andy
  • 61,948
  • 13
  • 68
  • 95
0

An idiomatic way would involve type checking. You can also leverage default parameters.

function test(firstArg, callback, options = {}) {
  if (typeof options == 'function') {
    callback = arguments[2];
    options = arguments[1];
  }

}

This not only ensures that you always have an options object but also works if someone switches the arguments for callback and options. So it doesn't matter if the options come in second or last. Moreover and maybe more importantly the source code clearly tells us its intention.

a better oliver
  • 26,330
  • 2
  • 58
  • 66
  • I agree this would work, but I disagree that it's idiomatic for ES6 or Node. In fact, I believe `arguments` is deprecated (or at least discouraged) in ES6, and I see very little type checking being done in JavaScript. Either way, I find the ability to pass in options in different orders a bad pattern, particularly because it's not general; it can only be done in the case where you can type check to determine which argument is which (would work fine in the specific case I outlined). – brianmearns Apr 07 '16 at 12:52
  • @sh1ftst0rm If you take a look at popular libraries you'll see that type checking is a common idiom. `arguments` is neither discouraged nor deprecated. In fact it has even been improved. Yet with the advent of the spread operator it is of limited use. If you want to talk about bad patterns, talk about an optional middle argument. If you find the ability to pass arguments in arbitrary order a bad pattern, then ask yourself if multiple arguments are a good thing to begin with. Now if you never ever in your life got the order wrong, then I'm willing to change my mind. – a better oliver Apr 07 '16 at 13:18
  • We can probably stand to take this down a notch. Bad pattern or not, optional middle arguments are extremely common in node, and there is a big difference between that and taking arguments in any order. Of course getting the argument order right is always a problem, but I think you're better off solving that with something like currying or a builder pattern. Letting the calling code swap the order of certain arguments, but not others is begging for trouble. It's better to just keep a fixed order and make them learn it, instead of sowing confusion by having different orders everywhere. – brianmearns Apr 07 '16 at 13:28