Joseph
Joseph

Reputation: 119837

Optional argument first in JavaScript

For the user's ease, I have a function that receives an optional argument first, before the required argument. For example:

ns.myFunction('optional arg', function(){
    //the required callback
});

I'm doing this rather than doing the following since the callback body could be long, and the user might forget to override the defaults to the optional arguments:

ns.myFunction(function(){
    //the required callback
}, 'optional arg');

Currently I'm doing this to check:

function myFunction(first, second) {

    //if second is undefined and first is a function
    if (typeof second === 'undefined' && typeof first === 'function') { 
        second = first;
    }
}

Questions

Upvotes: 8

Views: 5878

Answers (8)

Fida Hasan
Fida Hasan

Reputation: 61

function test(num = 1) {
  console.log(typeof num)
}

test()           // 'number' (num is set to 1)
test(undefined)  // 'number' (num is set to 1 too)

// test with other falsy values:
test('')         // 'string' (num is set to '')
test(null)       // 'object' (num is set to null)

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters

Upvotes: 0

Timar Ivo Batis
Timar Ivo Batis

Reputation: 1996

A simple functional approach:

Idea:

  1. Write a function myFunction(required, optional) where the optional argument is last, and thus easy to deal with.
  2. Use function appendFirstArgumentIf() to create a new function myNewFunction(optional, required) out of myFunction.
  3. Function appendFirstArgumentIf calls myFunction with the first argument moved to the last position if it passes a testfunction, otherwise it doesn't change the arguments.

_

 /**
 * @param {function} fn(a,...b)
 * @param {function} argumentTest - a function to test the first parameter
 * @return {function} returns a function that passes its first argument into 
  argumentTest. If argumentTest returns true, fn is called with the first 
  argument shifted to the last position else fn is called with the arguments 
   order unchanged
 */
function appendFirstArgumentIf (fn,argumentTest){
   return function(a,...b){
       return argumentTest(a) ? fn(...b,a) : fn(a,...b) 
   }
}

usage:

function myFunction(callback, string){
    string = string ? string : 'Default Message'
    callback(string)
}

var myNewFunction = appendFirstArgumentIf(myFunction,
                                          firstArgument=> typeof firstArgument !== 'function')


myNewFunction('New Message', console.log)  // using console.log as callback function
//>>>New Message
myNewFunction(console.log)
//>>>Default Message

also possible with this:

function mySecondFunction(callback, name, string){
string = string ? string : 'Welcome'
callback(string, name)
}

var myLatestFunction = appendFirstArgumentIf(mySecondFunction,
                                      firstArgument=> typeof firstArgument !== 'function')


myLatestFunction('Hello', console.log, 'John')
//>>>Hello John
myLatestFunction(console.log, 'John')
//>>>Welcome John

Upvotes: -1

Arsen K.
Arsen K.

Reputation: 5660

You can configurate function arguments, depending on their number:

myFunction() {
    let first, second;
    if (arguments.length === 2) {
        [first, second] = arguments;
    } else if (arguments.length === 1) {
        [second] = arguments;
    }
    if ( ! first) {
        // first is undefined, so only second argument was passed.
    }
}

ES6 destructuring makes code clean and suitable for scaling.

Upvotes: 2

Tomasz Nurkiewicz
Tomasz Nurkiewicz

Reputation: 340733

This is not the right way because optional parameters are by convention always placed at the end. And you see a reason why: it is much easier to handle them. If the length of anonymous function is your concern, clients of your API should use function references or variables:

function callback1() { //...

var callback2 = function() {//...

myFunction(callbackX, optional);

The problem with escaping this can be solved with bind().

If you really want to go the path of multiple optional parameters and callback at the end, I can think of two ways: arguments object or wrapping all optional arguments in one options objects.

With arguments you can write:

var lastOptionalIndex = arguments.length - 2;
var callback = arguments[lastOptionalIndex + 1];  //required callback is always last
var optionalFirst = lastOptionalIndex >=0? arguments[0] : undefined;
var optionalSecond = lastOptionalIndex >=1? arguments[1] : undefined;
//...

See how ugly it is compared to:

function myFunction(callback, firstOptional, secondOptional //...

With options wrapper object you always have two arguments:

function myFunction(options, callback);

Where options is just an object:

{
  firstOptional: 1,
  secondOptional: 'foo'
  //...
}

Upvotes: 4

CHAN
CHAN

Reputation: 1566

ArgueJS:

function range(){ 
  arguments = __({start: [Number, 0], stop: Number, step: [Number, 1]});
  for(var i = arguments.start; i < arguments.stop; i += arguments.step)
    console.log(i);
}

Become even prettier with ES6:

function range(){ 
  {start, stop, step} = __({start: [Number, 0], stop: Number, step: [Number, 1]})
  for(var i = start; i < stop; i += step)
    console.log(i);
}

Or CoffeeScript:

range = ->
  {start, stop, step} = __ {start: [Number, 0], stop: Number, step: [Number, 1]}
  console.log i for i in [start...stop] by step

Upvotes: 1

zVictor
zVictor

Reputation: 3717

It can easilly be done with ArgueJS:

function myFunction (){
  arguments = __({first: [String], second: Function})

  // and now on, you can access your arguments by
  //   arguments.first and arguments.second
}

A very similar example can be found at ArgueJS' first example. Note that only the parameter in the middle is required:

function range(){ 
  arguments = __({start: [Number, 0], stop: Number, step: [Number, 1]})

  for(var i = arguments.start; i < arguments.stop; i += arguments.step)
    console.log(i);
}

repl:

>>> range(3)
 0
 1
 2
>>> range(3, 5)
 3
 4
>>> range(0, 5, 2)
 0
 2
 4

Upvotes: 2

Matt Coughlin
Matt Coughlin

Reputation: 18906

It's not necessary to name any of the parameters. you can simply say:

if (arguments.length < 2)  // There are no optional parameters

And retrieve each parameter via arguments[i]. The function is found at arguments[arguments.length - 1].

On a minor note, the typeof operator always returns a string, so == can be used instead of ===

Upvotes: 1

mmaag
mmaag

Reputation: 1584

If you really want to pass multiple optional parameters first, you could do it like this:

function myFunction() {
    alert("optional:");
    for(var i=0; i<arguments.length-1) alert(arguments[i]);
    alert("function: "+ arguments[arguments.length-1]);
}

But It's rather ugly. I think you should just put the optional stuff at the end.

Upvotes: 0

Related Questions