Javascript Q vs callbacks to return $.ajax or $http data

An issue that often occurs when using Javascript is that you will a call a function, which will eventually return a value (not now ,but eventually). In my recent case this was with a function that calls $.ajax() function, and tries to return that value to the caller.

For this test I signed up at http://home.openweathermap.org/
and got a key to use, you’ll need to use either the sample key at their site or get a key if you want to try this

Here is a sample (this doesn’t work so don’t use this method)

 
    <html>
        <head>
            http://bower_components/q/q.js
            http://bower_components/jquery/dist/jquery.js
             
                function CallAPI( city)
                { 
                    var key = 'bd82977b86bf27fb59a04b61b657fb6f';
                    
                    var url = 'http://api.openweathermap.org/'
                    var fullurl = url + 'data/2.5/weather';
                   
                    $.ajax({
                        dataType: "jsonp",
                        url: fullurl,
                        jsonCallback: 'jsonp',
                        data: { q: city,
                              appid: key},
                        cache: false,
                        success: function (data) { 
                           return data;  //PROBLEM: you can't return this data now!!!!
                        } 
                      }); 
                }
                
               function GetWeather(){
                     var city = 'greensboro,us'
                    
                    var data = CallAPI(city); 
                    alert(data.weather[0].description);   //alert is undefined because this is hit long before the ajax call returns.
               }
            
        </head>
        <body>
            <button id='mybutton' onclick="GetWeather()">Get Weather</button>
        </body>
    </html>
 

The problem is that if I try to return the value in the .success section, it will be undefined when I try to use it in the calling method.

The javascript way to handle this is to pass a callback function, and once the AJAX call completes, you load your return value into the callback function, which is then used by the originating caller. This is a bit unnatural for those of us used to languages other than Javascript. Here is a sample of that solution. This works but passing callbacks rather than using ‘return’ feels awkward.

<html>
        <head>
            http://bower_components/jquery/dist/jquery.js
             
                function CallAPI( city,mycallback)  //receives a callback function
                { 

                    var key = 'bd82977b86bf27fb59a04b61b657fb6f';
                    
                    var url = 'http://api.openweathermap.org/'
                    var fullurl = url + 'data/2.5/weather';
                   
                    $.ajax({
                        dataType: "jsonp",
                        url: fullurl,
                        jsonCallback: 'jsonp',
                        data: { q: city,
                              appid: key},
                        cache: false,
                        success: function (data) { 
                            mycallback(data);		//put data in your callback function
                        } 
                      }); 
                }
                
               function GetWeather(){
                    var city = 'greensboro,us'
                    
                    var data = {}; //an empty object for now
                    CallAPI(city,function(data){ 
                    	//this executes whenver the data is sent back via the callback function in CallAPI
                    	//this is the call back function
                        alert(data.weather[0].description);      
                    }); 
                }
                
            
        </head>
        <body>
            <button id='mybutton' onclick="GetWeather()">Get Weather</button>
        </body>
    </html>

If you run that you will have a button on your screen that says ‘Get Weather’. This triggers the ‘GetWeather()’ function, which in turn calls ‘CallAPI(city,mycallback)’. The CallAPI function makes an ajax call to the API to get some weather info in the form of JSON data. Once the callback function runs in the original function (GetWeather()), an alert window displays the value I returned from ‘CallAPI’.

Using Q
The solution is Q (or $q in AngularJS).

Q is on GitHub at https://github.com/kriskowal/q, and can be installed in using Nuget, or if you rather use bower, you can install it with the command line ‘bower install q#1.0.1’.

There is also a variation of it included in AngularJS so you see the same functionality in AngularJS automatically without separately installing Q.

Now we’ll look at an example using Q. Notice that this method does the same thing but there is no callback function being passed from one function to another. Instead we use the more natural method of returning a value from a function.
Also note that this one looks longer because I’ve included error paths where I didn’t above.

<html>
        <head>
            http://bower_components/q/q.js
            http://bower_components/jquery/dist/jquery.js
             
                function CallAPI(city)
                { 
                    var key = 'bd82977b86bf27fb59a04b61b657fb6f';
                    
                    var url = 'http://api.openweathermap.org/'
                    var fullurl = url + 'data/2.5/weather';
                    
                    //I return this rather than working with callback functions
                    return $.ajax({
                            dataType: "jsonp",
                            url: fullurl,
                            jsonCallback: 'jsonp',
                            data: { q: city,
                                  appid: key},
                            cache: false 
                          })
                        .then(
                            function(response){
                                //Happy path -- returns expected data
                                return response;
                            },
                            function(response){
                                //Sad path -- an error occured
                                return response;
                            }
                          );
                       
                }
                
               function GetWeather(){
                     var city = 'greensboro,us'
                    
                     CallAPI(city)
                     .then(
                         function(data){
                            // Happy path -- this first function returns expected data
                            alert(data.weather[0].description);
                         },
                         function(data){
                             //Sad path -- this second function is hit if there was an error
                             alert ("So sad... There was an error : " + data);
                         }
                     );
                }
                
            
        </head>
        <body>
            <button id='mybutton' onclick="GetWeather()">Get Weather</button>
        </body>
    </html>

So what do we see here?
A ‘Get Weather’ button appears on your page. You click it. It triggers the ‘GetWeather’ function. This, in turn, calls ‘GetAPI(city)’, and because we are now using Q, GetAPI(city) promises to return a value (good or bad it will return something so we can rely on it).

We’ve chained a ‘.then(…)’ statement onto the $.ajax call, so our .ajax call can go do it’s work and take it’s time, but whenever it gets done, it will either have a valid response (promise fulfilled) or it will have an error (promise rejected). If all worked as planned, the first function within the .then section will be executed, and if an error occurs, the second function in the .then section will be executed. In both cases, I’ve set it to return that data back to the original caller. The original caller also uses the .then section with 2 functions so it can react to both positive and negative responses.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s