Question

Suppose you want to make an async request in JavaScript, but you want to pass some state along to the callback method. Is the following an appropriate use of closures in JavaScript?

function getSomethingAsync(someState, callback) {
    var req = abc.createRequestObject(someParams);
    req.invoke(makeCallback(someState, callback));
}

function makeCallback(someState, callback) {
    return function getSomethingCallback(data) {
        var result = processDataUsingState(data, someState);
        callback(result); // alternately/optionally pass someState along to result
    }
}

If not, is there a better or more idiomatic way?

Was it helpful?

Solution

I don't see any immediate problems with this - closures are powerful for numerous reasons, one of which is removing the need to use global variables for state maintenance.

That said, the only thing you need to be wary of with regards to closures is memory leaks that typically occur in IE, but those are usually, IIRC, related to DOM elements and event handlers attached thereto.

Proceed!

OTHER TIPS

The more idiomatic way is to use Function.bind, then you don't need to duplicate the code to create the closure. I'll use a simple example (that doesn't have your custom code) to explain

/**
 * Retrieves the content of a url asyunchronously
 * The callback will be called with one parameter: the html retrieved
 */
function getUrl(url, callback) {
    $.ajax({url: url, success: function(data) {
        callback(data);
    }})    
}

// Now lets' call getUrl twice, specifying the same 
// callback but a different id will be passed to each
function updateHtml(id, html) {
    $('#' + id).html(html);
}


// By calling bind on the callback, updateHTML will be called with 
// the parameters you passed to it, plus the parameters that are used
// when the callback is actually called (inside )
// The first parameter is the context (this) to use, since we don't care,
// I'm passing in window since it's the default context
getUrl('/get/something.html', updateHTML.bind(window, 'node1'));
// results in updateHTML('node1', 'response HTML here') being called
getUrl('/get/something-else.html', updateHTML.bind(window, 'node2'));
// results in updateHTML('node2', 'response HTML here') being called

Function.bind is new so if you need backwards support, look at the Compatibility section of Function.bind

Lastly, I know the question wasn't tagged as jQuery. This was just the quickest way to show an asynchronous function without dealing with cross browser issues

its better (nicer) to use anonymous functions:

function getSomethingAsync (someState, callback) {
    req.invoke (function (data) {
       var result = processDataUsingState (data, someState);
       callback (result);
    });
}

You can also manipulate the value of "this" in a function with call() / apply().

For example, specify that a further argument to your async method is an object to use as "this" in the callback. This is how jQuery sets the dom node to be "this" in event callbacks.

function getSomethingAsync(callback, scope) {
    var req = abc.createRequestObject(someParams);
    req.invoke(function(rsp) {
      callback.apply(scope, [rsp]);
    });
}

// usage:
getSomethingAsync(function() {console.log(this.someState)}, {someState:'value'});
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top