Escher
Escher

Reputation: 5776

How to delay execution of javascript function until JSON has loaded

I have a page that chains two API calls, loads the data into first_data and second_data before executing a createPage function (which is several kb of data manipulation and d3.js):

template.html

<script src="createPage.js"></script>
<script>
var first_data, second_data = [], [];

function getFirstData(){
    return new Promise(function(resolve) {
        var xhr = new XMLHttpRequest();
        var url = "/API/my-request?format=json"
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4 && xhr.status == 200) {
                first_data = JSON.parse(xhr.responseText);
                resolve('1');
            }
        }
        xhr.open("GET", url, true);
        xhr.send();
    });
} //similar function for getSecondData()

getFirstData()
    .then(getSecondData)
    .then(createPage(first_data, second_data));
</script>

The trouble is that some of the code that manipulates the data in createPage is showing errors, for example "can't convert undefined to object". In that particular error's case, it's because I try to do Object.keys(data[0]) on some data that should be loaded from the API requests. Some observations:

So, clearly I don't have access to the data at the point when I need it.

Upvotes: 0

Views: 1219

Answers (2)

kazenorin
kazenorin

Reputation: 1465

In functional programming and using promises, you should probably refactor getFirstData (and getSecondData) to the following form:

function getFirstData(){
    return new Promise(function(resolve, reject) {
        var xhr = new XMLHttpRequest();
        var url = "/API/my-request?format=json"
        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4 && xhr.status == 200) {
                // Resolve the result, don't assign it elsewhere
                resolve(JSON.parse(xhr.responseText));
            } else {
                // Add rejection state, don't keep the promise waiting
                reject("XHR Error, status = ", xhr.status); 
            }
        }
        xhr.open("GET", url, true);
        xhr.send();
    });
}

And then resolve the promises like this (assume first & second data is not dependant on each other)

Promise.all([getFirstData(), getSecondData()]).then(function(allData){
  var first_data = allData[0];
  var second_data= allData[1];
  return createPage(first_data, second_data);
}).catch(function(error){
  console.log("Error caught: ", error);
});

To make things even cleaner, you can change createPages's from:

function createPage(first_data, second_data){
  // Function body
}

to

function createPage(data){
  var first_data = data[0];
  var second_data= data[1];
  // Function body
}

and the Promise part:

Promise.all([getFirstData(), getSecondData()]).then(createPage);

Notice how short it became?

Upvotes: 1

Rayon
Rayon

Reputation: 36609

Promise.prototype.then() expects 2 arguments(onFulfilled & onRejected) as function-expression(OR handler or callback) as it is a function(handler) which will be invoked when Promise is fulfilled

In your case, createPage(first_data, second_data) will invoke the function createPage when statement is interpreted by interpreter.

Use anonymous function as an argument and invoke your function inside it.

getFirstData()
  .then(getSecondData)
  .then(function() {
    createPage(first_data, second_data);
  });

Edit: If you are not passing any arguments specifically to the callback, you can use .then(FUNCTION_NAME)

Upvotes: 1

Related Questions