2

Javascript : return XMLHttpRequest out of scope

I need to return the data from my AJAX call

series: [{
   data: (  )

in order to update one of the keys data in dictionary series but my function retrieve does not seem to return the data that I am getting.

var myPopulation = {
    series: [{
        data: (
            function() {
                function retrieve() {
                    var httpRequest = new XMLHttpRequest();
                    httpRequest.onreadystatechange = function() {
                        if (httpRequest.readyState === 4) {
                            if (httpRequest.status === 200) { 
                                var obj = JSON.parse(httpRequest.responseText)
                                console.log(obj.V1, obj.V2, obj.V3, obj.V4);
                                var data = [],
                                    time = (new Date()).getTime(),
                                    i;
                                for (i = -60; i <= 0; i++) {
                                    console.log(obj.V2)
                                    data.push({
                                        x: time + i * 60 * 1000,
                                        y: obj.V2
                                    });
                                }
                                myPopulation.series[1].data = data
                                // ???
                                console.log(data)
                            }
                        }
                    };
                    httpRequest.open('GET', "/myCall");
                    httpRequest.send();
                }
                retrieve();
            }()
        )
    }],

What should I do to return the value out of the function and update data?

  • You may provide your HTML as well and explain a bigger picture of what are you going to do. The problem may be you use a chart library in a wrong way. – Wenbing Li Aug 29 '14 at 03:21

6 Answers6

2

Well, since you are using jQuery tag, I think my answer could be valid and I prefer doing this way for what you need and I understood (it is well explained so please read code comments and check browser console, this can be found at the end of the answer).

Remember that you won't be able to return a XMLHttpRequest because ajax calls are async but you can force an ajax call to be sync in order to get your data on a return statement from any function or do other things as you expected. However, forcing is not a good approach to do because UI will freeze for the user until getting the response back from server and you really don't know how much time that will take (specially if you are expecting a big amount of data to be returned - I know that's not entirely a metric but other factors may apply).

Hope this helps and please take your time and read the following post and user comments: Reasons not to use native XMLHttpRequest - why is $.ajax mandatory?

Live Demo: http://jsfiddle.net/4mbjjfx8/


HTML:

<div id="wrapper">
    <div id="loader"></div>
    <div id="content"></div>
</div>

jQuery

$(function() {

    var series = [], // Your series array
        loader = $('#loader'), // UI loader sample
        request = {}; // Request params

    /**
     * Set request method, url and data if needed
     * In this case I am sending an object with a text property
     * that will be returned from jsfiddle "echo" service
     */
    request.method = 'GET';
    request.url = '/echo/jsonp/';
    request.data = {
        text: 'Second message returned from "echo" service'
    };

    // Send ajax call
    retrieveData(request, series, handleData);

    // Set loading message to UI
    loader.html('Loading...');

    // Just do some logging to know how process goes
    console.log('Populating series for the first time');

    /** 
     * Populate series for the first time, at this point process
     * will go on and after the response from server was finally
     * done, process will go to the callback (since ajax calls 
     * are async).
     */
    populate(series);

    // Just do some logging to know how process goes
    console.log('End populating series for the first time');
});

function populate(series) {
    var dummy = {
        text: 'First message populated over process'
    };

    // Set dummy object to series array
    series.push(dummy);
};

/**
 * Used to make ajax call and return data from server
 */
function retrieveData(cfg, series, callback) {
    $.ajax({
        type: cfg.method,
        url: cfg.url,
        data: cfg.data
     }).done(function(data, status, xhr) {
        // Pass args to callback function if defined
        if (callback) callback(series, data);
     }).fail(function(xhr, status) {
        /**
         * Pass args to callback function if defined
         * At this point, request wasn't success so 
         * force data arg at callback to be 'null'
         */
        if (callback) callback(series, null);
     });
};

/**
 * Used to handle data returned from server
 * Note: Your series array can be modified here since you
 * passed it into the callback
 */
function handleData(series, data) {
    var loader = $('#loader');

    // Just do some logging to know how process goes
    console.log('Populating series from server');

    // Check if data is defined and not an empty object
    if(data && !($.isEmptyObject(data))) {
        // Add it to series array
        series.push(data);
    }

    // Set UI loader empty
    loader.html('');

    // Retrieve series
    showData(series);
};

function showData(series) {
    var contentDiv = $('#content');

    // Loop process and append to UI
    for(var i = 0; i < series.length; ++i) {
        contentDiv.append(series[i].text + '<br>');
    }
};
Oscar Jara
  • 14,129
  • 10
  • 62
  • 94
1

You should put retrieve function outside. You can invoke retrieve function. And, It will call ajax. When ajax is success, it will update data of population. Like this.

var myPopulation = {
    series: [{
        data: undefined
    }]
};

function retrieve() {
    var httpRequest = new XMLHttpRequest();
    httpRequest.onreadystatechange = function() {
        if (httpRequest.readyState === 4) {
            if (httpRequest.status === 200) { 
                var obj = JSON.parse(httpRequest.responseText)
                console.log(obj.V1, obj.V2, obj.V3, obj.V4);
                var data = [],
                    time = (new Date()).getTime(),
                    i;
                for (i = -60; i <= 0; i++) {
                    console.log(obj.V2)
                    data.push({
                        x: time + i * 60 * 1000,
                        y: obj.V2
                    });
                }
                myPopulation.series[0].data = data
                // ???
                console.log(data)
            }
        }
    };
    httpRequest.open('GET', "/myCall");
    httpRequest.send();
}

retrieve();
manassorn
  • 450
  • 3
  • 17
1

Assuming you're simply trying to set the value of myPopulation.series[0].data when the array is first defined...

myPopulation.series[1].data = data

...should be...

myPopulation.series[0].data = data;

Also, some parts of you code are missing closing semicolons, closing brackets and/or curly brackets. Please make sure you end all statements with a semicolon and you have an equal number of opening and closing (curly) brackets.

I've tested your code with the above changes. The HTTP request I made returned a simple "Test successful" string, so I've replaced the code which handles the structure of the response text to simply var data = httpRequest.responeText;. This worked fine. Of course, this assumes the code which handles the structure of the returned httpRequest.responeText in your case is correct, as I have no way of knowing what the responseText in your case looks like. If you receive any errors regarding this part of your code, we'll need to see what the responseText looks like before we can help you.

Zekko
  • 73
  • 6
1

I'm not judging whether you are doing the right thing. Im merely presenting you a working version of your code.

Errors in your code:

  1. You mean to set result of the "function" to data but your function is not returning anything in the first place.
  2. XMLHttpRequest is async so even if you return you will not have the data set, simply because the outer function exited after making the http request setting a callback to trigger when it is completed.

Note: The fix is by making XMLHttpRequest synchronous.

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests

Here is the corrected version of your code

var myPopulation = {
series: [{
    data: (
        function() {
            function retrieve() {
                var httpRequest = new XMLHttpRequest();
                var result = []; //[1] just renamed data to result to avoid confusion
                httpRequest.onreadystatechange = function() {
                    if (httpRequest.readyState === 4) {
                        if (httpRequest.status === 200) { 
                            var obj = JSON.parse(httpRequest.responseText)
                            console.log(obj.V1, obj.V2, obj.V3, obj.V4);

                            var time = (new Date()).getTime(),
                                i;
                            for (i = -60; i <= 0; i++) {
                                console.log(obj.V2)
                                result.push({
                                    x: time + i * 60 * 1000,
                                    y: obj.V2
                                });
                            }
                            //myPopulation.series[1].data = data //[2] commented this as it is not necessary
                            // ???
                            console.log(result)
                        }
                    }
                };
                httpRequest.open('GET', "/myCall", false); //[3] Added 3rd argument 'false' to make the call synchronous
                httpRequest.send();

                return result //[4] to convey the result outside

            }
            return retrieve(); //[5] added return to set it to the data
        }()
    )
}],

The above code is not tested however. Here is a tested solution http://jsfiddle.net/98f9amo8/1/ The jsfiddle content is different for obvious reasons.

Gnani
  • 455
  • 5
  • 18
  • Make the call synchronous may work but it makes the page take forever to load –  Aug 29 '14 at 14:36
  • Make this sync is not a good approach, by the way.. why are you using jquery tag if there´s nothing related to it? @EPSILONsdfsdfdsf – Oscar Jara Aug 30 '14 at 02:03
1

Working with async code means you have to change the way you code because the code is not executed top-down any more.

So, in your case, you would do something like:

var myPopulation = {series: []}; 
$.get(..., my_function_that_will_format_the_data_after_they_have_been_received);
...
my_function_that_will_format_the_data_after_they_have_been_received() {
    // Do stuff here
    var formattedData = ...

    myPopulation.series.push(formattedData);
    // ONLY NOW, myPopulation is ... populated with data.
    // So, whatever you use this for, need to be called here
    doMagicWith(myPopulation);
}
...
/// Here, myPopulation is empty. doMagicWith(myPopulation) will fail here.
rollingBalls
  • 1,808
  • 1
  • 14
  • 25
0

I do not know the context of how you are doing this, seeing no jQuery tells me you wish to avoid it.

So no matter what happens the call is going to take time, and you need to wait for it for whatever you may need to do with it. Loaders can help tell a user that its processing but there are other ways to do that as well. The common factor is no matter what the data is not going to be there when you need it unless you do some sort of callback.

So here is an idea, create your on onload event more or less. There are many things to keep an eye on so jQuery's is probably the most complete, but going to keep it simple here.

window.isLoaded = false;
window.ajaxLoaded = false;
window.onload = function(){
   if(window.ajaxLoaded){
      onReadyFunction();
   }else{
      window.isLoaded = true;
   }
}

//skipping most of your code, the key part is the retrieve function. 
//So its the only part I am going to include in this part.

           function retrieve() {
                var httpRequest = new XMLHttpRequest();
                httpRequest.onreadystatechange = function() {
                    if (httpRequest.readyState === 4) {
                        if (httpRequest.status === 200) { 
                            var obj = JSON.parse(httpRequest.responseText)
                            console.log(obj.V1, obj.V2, obj.V3, obj.V4);
                            var data = [],
                                time = (new Date()).getTime(),
                                i;
                            for (i = -60; i <= 0; i++) {
                                console.log(obj.V2)
                                data.push({
                                    x: time + i * 60 * 1000,
                                    y: obj.V2
                                });
                            }
                            myPopulation.series[1].data = data
                            // ???
                            console.log(data)
                        }
                    }
                    //here is my only addition
                    if(window.isLoaded){
                       onReadyFunction();
                    }else{
                       window.ajaxLoaded = true;
                    }
                };
                httpRequest.open('GET', "/myCall");
                httpRequest.send();
            }

So all I am doing is adding another part to the typical DOM load. Waiting for the data you need to be available before it initialized the rest of the JS. Doing this you can keep the least downtime for your app (although it depends on where you are trying to get this data though). All you need is to define the onReadyFunction like so.

function onReadyFunction(){
   //all the rest of your JS here
}

This can be expanded and organized very easy, just a simple example to get started.

Jordan Ramstad
  • 169
  • 3
  • 8
  • 37