Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force missing parameters in JavaScript

Tags:

javascript

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.

like image 209
user1485520 Avatar asked Jun 27 '12 12:06

user1485520


4 Answers

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
};
like image 157
jbabey Avatar answered Oct 20 '22 05:10

jbabey


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.

like image 39
mattmanser Avatar answered Oct 20 '22 05:10

mattmanser


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?

like image 35
Vasiliy Faronov Avatar answered Oct 20 '22 05:10

Vasiliy Faronov


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).
like image 34
Niklas R Avatar answered Oct 20 '22 05:10

Niklas R