Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DRYing up JavaScript functions taking optional arguments and a callback

In Node.js it is, for several reasons, customary/recomended to pass a callback to a function as the last argument. There may also be one or more optional arguments, which we would like to pass before the callback. You end up with seeing a lot of very repetitive code, such as

// receiveMessages([options], [callback])
function receiveMessages(options, callback) {
   if(typeof options === 'function'){
      callback = options;
      options = {}; // or some other sensible default
   }
   //...
 }

Adding additional optional arguments means adding an additional check, of course:

 // through([dest], [options], [callback])
 function through(dest, options, callback) {
     if(typeof dest === 'function'){
        callback = dest;
        dest = noop();
        options = {};
     }else if(typeof options === 'function'){
        callback = options;
        options = {};
     }
     // ...
 }

Edit This pattern appears all over the standard library as well.

I can think of a few hacky ways of DRYing this up, but I'm wondering if anyone has an especially elegant or generic solution to get the arguments bound to the correct positional parameters.

like image 207
jjm Avatar asked Jun 29 '13 07:06

jjm


People also ask

What is fallback function in JavaScript?

js applications. A fallback function specifies an action that is performed whenever a call to a remote service fails. For example, you can use a fallback function to send a customized message about service failures to the client.

How do I make a callback optional?

To make the callback optional, we can just do this: function mySandwich(param1, param2, callback) { console. log('Started eating my sandwich. It has: ' + param1 + ', ' + param2); if (callback) { callback(); } } // No third argument, but no error because we check for the callback first.

What is the advantage of callback function in JavaScript?

The benefit of using a callback function is that you can wait for the result of a previous function call and then execute another function call. In this example, we are going to use the setTimeout() method to mimic the program that takes time to execute, such as data coming from the server.

What is the difference between callback and Higher-Order Functions?

Higher-Order Functions(HoF) and Callback Functions(CB) are different. Higher-Order Functions(HoF): A function that takes another function(s) as an argument(s) and/or returns a function as a value. Callback Functions(CB): A function that is passed to another function.


2 Answers

One thing that comes to my mind is method overloading. Technically this is not supported in JavaScript but there are some way to realize something like this. John Resig has an article about it in his blog: http://ejohn.org/blog/javascript-method-overloading/

In addition, here is my implementation, that is very similar to John Resig's and highly inspiried by it:

var createFunction = (function(){
    var store = {};

    return function(that, name, func) {
        var scope, funcs, match;

        scope = store[that] || (store[that] = {});
        funcs = scope[name] || (scope[name] = {});

        funcs[func.length] = func;

        that[name] = function(){
            match = funcs[arguments.length];
            if (match !== undefined) {
                return match.apply(that, arguments);
            } else {
                throw "no function with " + arguments.length + " arguments defined";
            }
        };
    };
}());

This enables you to define the same function several times with a different number of arguments each time:

createFunction(window, "doSomething", function (arg1, arg2, callback) {
    console.log(arg1 + " / " + arg2 + " / " + callback);
});

createFunction(window, "doSomething", function (arg1, callback) {
    doSomething(arg1, null, callback);
});

This piece of code defines a global function doSomething, one time with three and one time with two arguments. As you see, the first downside of this method is that you have to provide an object to which the functions are being attached, you cannot just say "define a function right here". Also, the function declarations are a little bit more complicated then before. But you can now call your function with a different number of arguments and getting the right result without the use of repeating if..else structures:

doSomething("he", function(){});         //he / null / function(){}
doSomething("he", "ho", function(){});   //he / ho / function(){}

So far, only the number of arguments matter but I can think of extending this, to also react on different data types, so that one can also differentiate between the following:

function doSomething(opt1, opt2, callback){
    //some code
}

doSomething({anObject: "as opt1"}, function(){});
doSomething("a string as opt2", function(){});

Nevertheless, this is probably not the best way to go but it can be convinient in certain circumstances. For a lot of optional arguments, I personally like Pumbaa80's answer of putting those options in one required object-argument.

like image 76
basilikum Avatar answered Oct 10 '22 15:10

basilikum


In Node.js it is, for several reasons, customary/recomended to pass a callback to a function as the last argument

The only reason I can think of is forcing the developers to provide the other arguments.

For example, function (success, error) would result in sloppy programming, since lazy coders would simply omit the error callback. That's why you commonly see function (error, success).

That being said, the convention above is great for mandatory arguments. If you want optional arguments, just don't do that. One way to handle this scenario is the following scheme:

function (required1, required2, ..., callback, options)
// or
function (required1, required2, ..., options, callback)

where each optional argument may or may not be provided as an attribute of options.

Edit: actually, this is used in some node libraries, e.g. http://nodejs.org/api/fs.html#fs_fs_appendfile_filename_data_options_callback

like image 35
user123444555621 Avatar answered Oct 10 '22 14:10

user123444555621