Passing arguments to a JavaScript callback function

In JavaScript, callback functions are used to perform an action or operation after another operation has occurred. The mechanics of a callback rely on the fact that everything in JavaScript is a first class object. This fact allows you to pass functions as arguments to another function. The latter function can then invoke that function at a predetermined point in the program.

Let’s have a look:

// whatTime definition with callback as an argument
function whatTime(str, callback){
    if (str === 'hammer time') {
    // invoke callback
        callback();
    }
}

// Invoke whatTime with a string and an anonymous function (or callback)
whatTime('hammer time', function () {
    alert('STOP! You can\'t touch this!');
});

In the above example, we pass an anonymous function into whatTime as our callback argument. For those comfortable with JavaScript this is a common pattern but it can be hard to read. It is the same as this example, which passes a function name as the callback instead:

// whatTime definition with callback as an argument
function whatTime( str, callback ){
    if ( str === 'hammer time' ) {
        callback();
    }
}

// hammerTime definition
function hammerTime() {
    alert( 'STOP! You can\'t touch this!' );
}

// Invoke whatTime with a string and hammerTime
whatTime( 'hammer time', hammerTime );

This pattern of callbacks is so prevalent that it is the foundation of all JavaScript frameworks and libraries:

// jQuery
jQuery(function () {
    // jquery code here
});

// AngularJS
angular.module('app',[])
.controller('ViewController', function() {
    // controller code here
});

The patterns that developers build into their methods rely on callbacks as a form of API. The developer of the method doesn’t always know what you want to do once that methods main task is done and the data or transformations are available, so the callback gives you the option handle the return accordingly. This is especially true with asynchronous tasks.

XHR’s are a prime example of this. Consider the following AngularJS example asynchronously getting data from a server (notice that I don’t condone the use of anonymous callback functions):

$http.get( '/some/endpoint' )
.then( magicCallback );

function magicCallback( response ) {

    // what you plan to do once the
    // the XHR is successful

    if ( response.data ) {
        console.log( 'The data is: ', response.data );
    }
}

By default there is only one argument available to your magicCallback function, the response object that is returned as a promise from $http.get(). What if you want to do more conditional magic on the response that is difficult to determine from the response itself. For example, you want to write one function that handles different contexts in which it’s called. In this case it would be handy to pass more arguments to the callback. Consider the following contrived example — I want to handle two different ajax calls with one callback function. If I’m making a request to user/:id I want to do one thing, but I want to do another thing if I make a call to report/:id:

$http.get( '/user/' + vm.userId )
.then( magicCallback );

$http.get( '/report/' + vm.reportId )
.then( magicCallback );

function magicCallback( response ) {

    // I want to do something if
    // '/user/:id' was called, but
    // I have another idea if
    // '/report/:id' was called...
    //
    // but how?

    var data = void 0
        message = void 0;

    if ( response.data ) {
        data = response.data;


        if ( uhhhhhhhhhhhh ) {
            message = 'Hello ';
        } else {
            message = 'Here is report ';
        }


        message += uhhhhhhhhhhhh + ': ';

        console.log( message, data );
    } else {
        throw e;
    }
}

In the example above, we’d love to know the context that the function was called in. This is, again, where functions as first class citizens comes in handy and other functional programming trick called currying. Remember, as a first class object, not only can functions be passed as arguments, but they can be returned as values too. With currying, we harness this power to execute a function when it’s used as an argument, but that function returns another function that gets used as our callback.

Here is how we’d rewrite our magic callback example above:

$http.get( '/user/' + vm.userId )
.then( magicCallback( 'user' ) );

$http.get( '/report/' + vm.reportId )
.then( magicCallback( 'report' ) );

function magicCallback( context ) {
    return function( response ) {

        var data = void 0
            message = void 0;

        if ( response.data ) {
            data = response.data;


            if ( context === 'user' ) {
                message = 'Hello ';
            } else {
                message = 'Here is report ';
            }

            console.log( message, data );

        } else {
            throw e;
        }
    };
}

What happens above is that the return value of the execution of magicCallback( 'user' ) is passed in as an argument. Then internally, when the .then() method executes it’s callback function, the context of 'user' is already a value. It’s the equivalent, within the inner workings of the .then() method, of the following:

callback = magicCallback( 'user' );
callback( response );

or

callback( 'user' )( response ); 

Have any more interesting tips and tricks with regards to callbacks, currying or first class objects? Leave a comment.

 

Adam Merrifield

 

One thought on “Passing arguments to a JavaScript callback function

  1. Honestly, this is a big part of what I like about ES2015 fat-arrow syntax combined with rest/spread, which is easier to reason about…

    $http.get( '/user/' + vm.userId )
    .then( (...args) => magicCallback( 'user' , ...args) );

    Since you are taking in args, and passing them along with a prefix to magicCallback.

Leave a Reply