scullytr
scullytr

Reputation: 115

Variable Scope in Nested AJAX Calls

I have a set of custom user data that I want to make an ajax call to, and in the event that there is no user data, make another ajax call to retrieve a default set of data, and then execute a function after parsing the data. Here's an example:

var oData = [],
    exampleUrl = 'example.php';
$.ajax({
    url:    exampleUrl + '?query=getUserData',
    contentType:    'application/json;odata=verbose',
    headers:        {
        'accept':   'application/json;odata=verbose'
    },
    success : function(data, request){
        // Request succeeded
        // Check the results
        if(data.length){
            // There are custom user results!
            // Parse the results
            oData = data;
        }
        else{
            // There were no custom user results...
            // Run another query to retrieve default values
            $.ajax({
                url:    examplUrl + '?query=getDefaultData',
                contentType:    'application/json;odata=verbose',
                headers:        {
                    'accept':   'application/json;odata=verbose'
                },
                success : function(data, request){
                    // Request succeeded
                    // Check the results
                    if(data.length){
                        // There was some default data!
                        // Parse the results
                        oData = data;
                    }
                    else{
                        // No data was found...
                        // Attempt to be helpful
                        console.log('No Default data was found!');
                    }
                },
                error : function(data, request){
                    // There was an error with the request
                    // Attempt to be helpful
                    console.log('Error retrieving data:');
                    console.log(data);
                    console.log(request);
                }
            });

        }
    },
    error : function(data, request){
        // There was an error with the request
        // Attempt to be helpful
        console.log('Error retrieving Custom User data:');
        console.log(data);
        console.log(request);
    },
    complete : function(){
        // Do something with the data
        index.displayData(oData);
    }
});

The issue is that if the second ajax call is run, oData doesn't contain any data at all when it's passed to index.displayData(). I'm guessing it has something to do with the asyncronous nature of ajax calls, but shouldn't 'complete' run after everything inside of 'success' runs?

I also know I probably shouldn't be using the ajax "Pyramid of Doom" and should be using promises, but I've tried them and keep getting the same results.

Thank you for your assistance!

Upvotes: 2

Views: 1063

Answers (2)

Roamer-1888
Roamer-1888

Reputation: 19288

By working with promises, you can avoid the need to pass a callback into your function, and by defining a utility function you can avoid repetition of code.

//reusable utility function, which returns either a resolved or a rejected promise
function fetchData(queryString, cache) {
    return $.ajax({
        url: 'example.php',
        data: { query: queryString },
        type: 'JSON',//assumed
        cache: cache,
        contentType: 'application/json;odata=verbose',
        headers: { 'accept': 'application/json;odata=verbose' }
    }).then(function(data, textStatus, jqXHR) {
        if (data && data.length) {
            return data;
        } else {
            return $.Deferred().reject(jqXHR, 'no data returned').promise();//emulate a jQuery ajax failure
        }
    });
}

This allows promise methods to be used for a control structure, which :

  • is concise
  • uses chaining, not nesting
  • gives meaningful error messages.
//control structure
fetchData('getUserData', false).then(null, function(jqXHR, textStatus) {
    console.log('Error retrieving Custom User data: ' + textStatus);
    return fetchData('getDefaultData', true);
}).then(index.displayData, function(jqXHR, textStatus) {
    console.log('Error retrieving default data: ' + textStatus);
});

Notes :

  • the null in .then(null, function(){...}) allows a successful response to drop straight through to the second .then(index.displayData, ...)
  • default data is cached while the user data is not. This is not necessary to make things work but will be faster next time the default data is required.
  • in the world of promises, this or something similar is the way to go.

Upvotes: 1

hugomg
hugomg

Reputation: 69934

As pointed out by Violent Crayon, you could try calling "complete" yourself instead of relying on JQuery's implicit control flow:

function getData(exampleUrl, onComplete){
    $.ajax({
        success : function(data, request){
            if(data.length){
                onConplete(data);
            }else{
                $.ajax({
                    success : function(data, request){
                        if(data.length){
                            onComplete(data);
                        }else{
                            console.log('No Default data was found!');
                        }
                    },
                    error : function(data, request){
                        console.log('Error retrieving data:');
                    }
                });
            }
        },
        error : function(data, request){
            console.log('Error retrieving Custom User data:');
        }
    });
}

var oData = [];
getData('example.php', function(data){
    oData = data;
    index.displayData(oData);
}

BTW, note how you can have your async functions receive their own return and error callbacks. This can help reduce the pyramid of doom problem without needing to use promises and without needing to hardcode the return callback.

Upvotes: 3

Related Questions