Thursday, December 26, 2013

The power of javascript closures

Recently, I've fixed several issues in my web application where "sometimes things work, and sometimes they don't".  When I hear statements like that, often it points to a UI javascript timing issue.
  1. Checking if a ajax call returns any data OUTSIDE of the ajax success block.  For example with jQuery:
  2. doSomething: function()
    {
        var isData = false;
        $.ajax({
            url: url,
            type: "GET",
            success: function (jsonString) {
                // ... do some stuff
                isData = true;
            }
        });
        return isData;
    };
    
  3. Performing DOM manipulations with ajax return values OUTSIDE of the ajax success block.  For example:
  4. doSomething: function()
    {
        var jsonObj;
        $.ajax({
            url: url,
            type: "GET",
            success: function (jsonString) {
                jsonObj = jQuery.parseJSON(jsonString);
            }
        });
        
        // ... Manipulate DOM here with jsonObj data
    };
    

Since ajax calls are meant to be asynchronous, the call can return at any time.  You can't guarantee that the call will have returned when the red highlighted code is reached.

Both issues can be solved with Javascript closures.  Closures are powerful - developers often do not understand how (or when) to use them - so don't, or they are used incorrectly.  I've found this article to be the best, clearest, succinct article I have read on the subject.  In short - "A closure is a special kind of object that combines two things: a function, and the environment in which that function was created. The environment consists of any local variables that were in-scope at the time that the closure was created".

Closures work well with ajax calls, since you most likely want to display the return values of an ajax call on your web application.  The following are two examples where we explore DOM manipulation after an ajax call has completed in the success block.

EXAMPLE SOLUTIONS
We want to display the location "New York City" that belongs to the postal code "10014" when the "Find Postal" button is clicked.
  1. The simplest, most straight forward solution is DOM manipulation directly in the ajax success block of the performSearch function.  Take a look at the jsfiddle example http://jsfiddle.net/jhsu9/jAN6t/.
  2.     $.ajax({
            url: url,
            type: "GET",
            success: function (jsonString) {
                var jsonObj = jQuery.parseJSON(jsonString);
                var html = jsonObj.postalcodes[0].placeName;
    
                postalDiv.html(html);
            },
            error: function (xhr, textStatus) {
                alert("error");
            }
    
        }); // matches ajax end
    

  3. DOM manipulation happens with a closure, passed into the performSearch function as a callback function, and is invoked after the success block completes.  Take a look at the jsfiddle example http://jsfiddle.net/jAN6t/7/.
  4. performSearch = function (pCallback) {
        var url = "http://www.geonames.org/postalCodeLookupJSON?postalcode=10014&country=US";
    
        $.ajax({
            url: url,
            type: "GET",
            success: function (jsonString) {
                var jsonObj = jQuery.parseJSON(jsonString);
                var htmlData = jsonObj.postalcodes[0].placeName;
    
                // pCallback may either not exist or is undefined, checking for typeof callback == 'undefined' takes care of both
                if (!(typeof pCallback == 'undefined') && (typeof pCallback === "function")) {
                    // invoke the pCallback passed in, pass in any params if necessary
                    pCallback(htmlData);
                }
            },
            error: function (xhr, textStatus) {
                alert("error");
            }
    
        }); // matches ajax end
    
    };
    
    postalBtn.click(function () {
        // function to do the work
        function processFunction(pHtmlData)
        {
            alert("processFunction with data[" + pHtmlData + "]");
            postalDiv.html(pHtmlData);
        }
        // function factory, this will be passed into performSearch.
        // Essentially the callback will be a closure attached as a parameter to performSearch.
        // It's not until the callback is INVOKED will the inner function be called
        function processFunctionCallback()
        {
            alert("attached processFunctionCallback");
            return function(pHtmlData) {
                alert("invoke processFunctionCallback with data[" + pHtmlData + "]");
                processFunction(pHtmlData);
            };
        }
        
        performSearch(processFunctionCallback());
    });
    

    The performSearch function is passed an optional closure called processFunctionCallback() which has an inner function that is doing the actual work of updating the DOM; it's not until the closure/callback function is invoked that the inner function will be called.  Notice the data value "htmlData" has to be passed as a parameter to the closure / callback function in the ajax success block, in order for the inner function to know what value to display.

SUMMARY
Example Solution 2 is much more powerful.  The performSearch function can be invoked with various actions (i.e. Search button action, sort action, sort by column action, select all action, etc), so you want a generic way to do DOM manipulation after the ajax success completes.  By passing in a closure / callback function, each function can be unique as to what kind of DOM manipulation you want to happen after the performSearch completes.


No comments:

Post a Comment

I appreciate your time in leaving a comment!