Dynamically resolving function shared arguments in JavaScript

Sometimes we have functions which expect the same arguments as other functions, all fine there. Sometimes these arguments are obtained/resolved asynchronously. If there are a lot of these functions that share the same resource, we can come up with lot of unnecessary boilerplate.

Imagine having a function like resolveMetaData() which asynchronously obtains fresh data every time it’s called (to keep the code at a very simple level, for the purpose of this post I’ll be using setTimeout() instead of something a bit more complex like an AJAX call):

1
2
3
4
5
6
7
8
9
10
11
function resolveMetaData(callback) {
    // for the purpose of the demo we'll simply mock the metaData object    
    var metaData = { message: "Meta message", start: new Date() };

    // async business logic example
    setTimeout(function () {
        metaData.end = new Date();
        // after metaData is ready, resolve callback
        callback(metaData);
    }, 1000);
}

And two functions that require a new instance of metaData upon their execution:

1
2
3
4
5
6
7
8
9
10
function fnOne(data, metaData) {
    console.log(data);
    console.log(metaData);
}

function fnTwo(id, data, metaData) {
    console.log(id);
    console.log(data);
    console.log(metaData);
}

The simplest way to provide the latest metaData to these functions would be to use callbacks like this:

1
2
3
4
5
6
7
resolveMetaData(function(metaData) {
    fnOne("fnOne", metaData);
});

resolveMetaData(function(metaData) {
    fnOne(1, "fnTwo", metaData);
});

If you had to write a lot of functions similar to fnOne() and fnTwo() (ie. 10 or more) and all of them required the latest metaData, you would most probably be tempted to somehow reduce the code and get rid of the callback boilerplate. The first two ideas that came to my mind on how to resolve this were function overloads and/or having a base function that would handle metaData resolving. Since JS doesn’t really support overloading (in the same way as say C# does), having a base function to handle metaData resolving seems like a safe bet. The only question is – how do we call a function in JS with the parameters we got and resolve the shared parameters asynchronously?

Fortunately Function.prototype.apply() comes to the rescue! It allows us to call a function with arguments as an array which is quite handy. Since functions in JS are objects, we can now create a base function which accepts the function object of the function we wish to call, and the args we have at that point. It then resolves metaData, appends it to the arguments array and calls the passed function with these arguments. This is how the base function would look like:

1
2
3
4
5
6
function fnBase(fn, args) {
    resolveMetaData(function (metaData) {
        args.push(metaData);
        fn.apply(this, args);
    });
}

And this is the how we can now call fnOne() and fnTwo() through the fnBase():

1
2
fnBase(fnOne, ["fnOne"]);
fnBase(fnTwo, [1, "fnTwo"]);

It would be possible to place metaData as a first argument in fnOne() and fnTwo() signatures but that would require additional args position handling in fnBase() so it is probably best to put metaData as the last argument.

That’s it, hope it helps. Enjoy! :)