5

When you call a function in JavaScript and you miss to pass some parameter, nothing happens.

This makes the code harder to debug, so I would like to change that behavior.

I've seen How best to determine if an argument is not sent to the JavaScript function but I want a solution with a constant number of typed lines of code; not typing extra code for each function.

I've thought about automatically prefixing the code of all functions with that code, by modifying the constructor of the ("first-class") Function object.

Inspired by Changing constructor in JavaScript I've first tested whether I can change the constructor of the Function object, like this:

function Function2 () {
    this.color = "white";
}

Function.prototype = new Function2();
f = new Function();
alert(f.color);

But it alerts "undefined" instead of "white", so it is not working, so I've don't further explored this technique.

Do you know any solution for this problem at any level? Hacking the guts of JavaScript would be OK but any other practical tip on how to find missing arguments would be OK as well.

Community
  • 1
  • 1
user1485520
  • 61
  • 1
  • 3
  • 2
    This approach sounds like a fantastic way to break all third-party code that relies on this behaviour in JS. – millimoose Jun 27 '12 at 12:42
  • You're attempting to alter a fundamental aspect of the language. It's a misguided effort. – Pointy Jun 27 '12 at 12:46
  • Simple one liner, as long as you aren't passing "falsy" args on purpose: `if (!foo) { throw new Error('Missing argument "foo"!'); }`. Doesn't scale well with many arguments.... but long parameter lists are an anti-pattern, anyway. – Chris Collett May 26 '22 at 17:49

4 Answers4

10

If a function of yours requires certain arguments to be passed, you should check for those arguments specifically as part of the validation of the function.

Extending the Function object is not the best idea because many libraries rely on the behavior of defaulting arguments that are not passed (such as jQuery not passing anything to it's scoped undefined variable).

Two approaches I tend to use:

1) an argument is required for the function to work

var foo = function (requiredParam) {
    if (typeof requiredParam === 'undefined') {
        throw new Error('You must pass requiredParam to function Foo!');
    }

    // solve world hunger here
};

2) an argument not passed but can be defaulted to something (uses jQuery)

var foo = function (argumentObject) {
    argumentObject = $.extend({
        someArgument1: 'defaultValue1',
        someArgument2: 'defaultValue2'
    }, argumentObject || {});

    // save the world from alien invaders here
};
jbabey
  • 45,965
  • 12
  • 71
  • 94
2

As others have said, there are many reasons not to do this, but I know of a couple of ways, so I'll tell you how! For science!

This is the first, stolen from Gaby, give him an upvote! Here's a rough overview of how it works:

//example function
function thing(a, b, c) {

}

var functionPool = {} // create a variable to hold the original versions of the functions

for( var func in window ) // scan all items in window scope
{
  if (typeof(window[func]) === 'function') // if item is a function
  {
    functionPool[func] = window[func]; // store the original to our global pool
    (function(){ // create an closure to maintain function name
         var functionName = func;
         window[functionName] = function(){ // overwrite the function with our own version
         var args = [].splice.call(arguments,0); // convert arguments to array
         // do the logging before callling the method
         if(functionPool[functionName].length > args.length)
              throw "Not enough arguments for function " + functionName + " expected " + functionPool[functionName].length + " got " + args.length;                     
         // call the original method but in the window scope, and return the results
         return functionPool[functionName].apply(window, args );
         // additional logging could take place here if we stored the return value ..
        }
      })();
  }
}

thing(1,2 ,3); //fine
thing(1,2); //throws error

The second way:

Now there is another way to do this that I can't remember the details exactly, basically you overrride Function.prototype.call. But as it says in this question, this involves an infinite loop. So you need an untainted Function object to call, this is done by a trick of turning the variables into a string and then using eval to call the function in an untainted context! There's a really great snippet out the showing you how from the early days of the web, but alas I can't find it at the moment. There's a hack that's required to pass the variables properly and I think you may actually lose context, so it's pretty fragile.

Still, as stated, don't try and force javascript to do something against its nature, either trust your fellow programmers or supply defaults, as per all the other answers.

Community
  • 1
  • 1
mattmanser
  • 5,719
  • 3
  • 38
  • 50
1

You could use the decorator pattern. The following decorator allows you to specify minimum and maximum number of arguments that need to be passed and an optional error handler.

/* Wrap the function *f*, so that *error_callback* is called when the number
   of passed arguments is not with range *nmin* to *nmax*. *error_callback*
   may be ommited to make the wrapper just throw an error message.
   The wrapped function is returned. */
function require_arguments(f, nmin, nmax, error_callback) {
    if (!error_callback) {
        error_callback = function(n, nmin, nmax) {
            throw 'Expected arguments from ' + nmin + ' to ' + nmax + ' (' +
                  n + ' passed).';
        }
    }
    function wrapper() {
        var n_args = arguments.length;
        console.log(n_args, nmin, nmax);
        console.log((nmin <= 0) && (0 <= nmax));
        if ((nmin <= n_args) && (n_args <= nmax)) {
            return f.apply(this, arguments);
        }
        return error_callback(n_args, nmin, nmax);
    }
    for (e in f) {
        wrapper[e] = f[e];
    }
    return wrapper;
}


var foo = require_arguments(function(a, b, c) {
    /* .. */
}, 1, 3);
foo(1);
foo(1, 2);
foo(1, 2, 3);
foo(1, 2, 3, 4); // uncaught exception: Expected arguments from 1 to 3 (4 passed).
foo(); // uncaught exception: Expected arguments from 1 to 3 (0 passed).
Niklas R
  • 16,299
  • 28
  • 108
  • 203
1

You can imitate something like Python’s decorators. This does require extra typing per function, though not extra lines.

function force(inner) {
    return function() {
        if (arguments.length === inner.length) {
            return inner.apply(this, arguments);
        } else {
            throw "expected " + inner.length +
                " arguments, got " + arguments.length;
        }
    }
}

var myFunc = force(function(foo, bar, baz) {
    // ...
});

In general this sounds like a bad idea, because you’re basically messing with the language. Do you really forget to pass arguments that often?

Vasiliy Faronov
  • 11,840
  • 2
  • 38
  • 49
  • The decorator pattern is not Python specific. – Niklas R Jun 27 '12 at 12:59
  • Thanks to all. I think I will use either the Vasily's proposal alone, or within a loop as suggested by mattmanser, but that iterates _only_ through my own code as suggested by all (to not break third-party code). – user1485520 Jun 28 '12 at 16:23
  • Answering to Vasily: each time I do some moderate redesign of my code I end up with several function calls missing parameters. These situations usually take several minutes of superboring debugging. It is what I try to avoid. Of course, being more concentrated while refactoring would also help me :) – user1485520 Jun 28 '12 at 16:54