Javascript JSON for each with array of feeds

Viewed 91

In my example I am trying to fetch the ID and Description from each feed listed in my array feedUrls, but the order of how each feed is fetched doesn't match.

As shown below the result is not from first to last feed in my array, although they the feeds are listed in the array feedUrls from 001 to 005.

var feedUrls = ["https://api.myjson.com/bins/nkvdl", "https://api.myjson.com/bins/13wd2h", "https://api.myjson.com/bins/1b1kbt", "https://api.myjson.com/bins/10zc7d", "https://api.myjson.com/bins/60sqx"];

var arrayData1 = [];
var arrayData2 = [];

var getJSON = function(url, callback) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.responseType = 'json';
  xhr.onload = function() {
    var status = xhr.status;
    if (status === 200) {
      callback(null, xhr.response);
    } else {
      callback(status, xhr.response);
    }
  };
  xhr.send();
};

feedUrls.forEach(function(entry) {
getJSON(entry,
  function(err, data) {
    if (err !== null) {
      alert('Something went wrong: ' + err);
    } else {
      try {
        arrayData1.push("<li>" + data[0].id + "</li>");
      } catch (e) {}
      document.getElementById('listId1').innerHTML = arrayData1.join("");
    }
  });
});

feedUrls.forEach(function(entry) {
getJSON(entry,
  function(err, data) {
    if (err !== null) {
      alert('Something went wrong: ' + err);
    } else {
      try {
        arrayData2.push("<li>" + data[0].title + "</li>");
      } catch (e) {}
   document.getElementById('listId2').innerHTML = arrayData2.join("");
    }
  });
});
ul {
display: inline-block;
}
<ul id="listId1">empty</ul>
<ul id="listId2">empty</ul>

Result:

001
005
004
003
002
title 001
title 004
title 002
title 005
title 003

Expected:

001
002
003
004
005
title 001
title 002
title 003
title 004
title 005 
3 Answers

forEach() provides an index to the callback, which you can use in order to collect the results in their expected order, something like

var feedUrls = ["https://api.myjson.com/bins/nkvdl", "https://api.myjson.com/bins/13wd2h", "https://api.myjson.com/bins/1b1kbt", "https://api.myjson.com/bins/10zc7d", "https://api.myjson.com/bins/60sqx"];

var arrayData1 = [];
var arrayData2 = [];

var getJSON = function(url, callback, index) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.responseType = 'json';
  xhr.onload = function() {
    var status = xhr.status;
    if (status === 200) {
      callback(null, xhr.response, index);
    } else {
      callback(status, xhr.response, index);
    }
  };
  xhr.send();
};

feedUrls.forEach(function(entry, index) {
getJSON(entry,
  function(err, data, index) {
    if (err !== null) {
      alert('Something went wrong: ' + err);
    } else {
      try {
        arrayData1[index]="<li>" + data[0].id + "</li>";
      } catch (e) {}
      document.getElementById('listId1').innerHTML = arrayData1.join("");
    }
  },index);
});

feedUrls.forEach(function(entry, index) {
getJSON(entry,
  function(err, data) {
    if (err !== null) {
      alert('Something went wrong: ' + err);
    } else {
      try {
        arrayData2[index]="<li>" + data[0].title + "</li>";
      } catch (e) {}
   document.getElementById('listId2').innerHTML = arrayData2.join("");
    }
  },index);
});
ul {
display: inline-block;
}
<ul id="listId1">empty</ul>
<ul id="listId2">empty</ul>

The new part is the index variable.

Otherwise what the comment says: you have a number of independent XHR-s, the order of their completion has no guarantees.

You are using asynchronized request. Asynchronized request will respond in random order. You may never know when it will arrive. And this is expected behavior in ajax. But if you insist that response order REALLY matters, here's the solution for you:

<ul id="listId1">empty</ul>
<ul id="listId2">empty</ul>
<script>
  var feedUrls = ["https://api.myjson.com/bins/nkvdl", "https://api.myjson.com/bins/13wd2h", "https://api.myjson.com/bins/1b1kbt", "https://api.myjson.com/bins/10zc7d", "https://api.myjson.com/bins/60sqx"];

var arrayData1 = [];
var arrayData2 = [];

var getJSON = function(url, callback) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url, false);
  xhr.send(null);
  var status = xhr.status;
    if (status === 200) {
      callback(null, xhr.response);
    } else {
      callback(status, xhr.response);
    }

};

feedUrls.forEach(function(entry) {
getJSON(entry,
  function(err, data) {
    if (err !== null) {
      alert('Something went wrong: ' + err);
    } else {
      try {
        var data = JSON.parse(data);
        arrayData1.push("<li>" + data[0].id + "</li>");
      } catch (e) {}
      document.getElementById('listId1').innerHTML = arrayData1.join("");
    }
  });
});

feedUrls.forEach(function(entry) {
getJSON(entry,
  function(err, data) {
    if (err !== null) {
      alert('Something went wrong: ' + err);
    } else {
      var data = JSON.parse(data);
      try {
        arrayData2.push("<li>" + data[0].title + "</li>");
      } catch (e) {}
      document.getElementById('listId2').innerHTML = arrayData2.join("");
    }
  });
});
</script>
<style>
ul {
display: inline-block;
}
</style>

Notice that the xhr request is opened with the third parameter false. This implies that xhr object is sync. (Although it's deprecated.) And sync xhr cannot set response type. So I had to parse the response in callback functions. Hope it helps!

Here is an updated version using Promise to make the API calls once then sort and display the data.

We do this because

  1. We should only call the API again if we need new or updated data, not because we only grabbed one property and we need two
  2. Promise instead of callback because it allows us to chain these together and "wait for all"
  3. For a specific order, we need to make all the API calls and then we can sort the data (as one collection of data vs. two individual arrays)

const feedUrls = ["https://api.myjson.com/bins/nkvdl", "https://api.myjson.com/bins/13wd2h", "https://api.myjson.com/bins/1b1kbt", "https://api.myjson.com/bins/10zc7d", "https://api.myjson.com/bins/60sqx"];

const arrayData = [];

const getJSON = (url, callback) => {
  // use a Promise to be able to use .then
  // and Promise.all so that we can process
  // all of the API calls before subsequent code
  const promise = new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url, true);
    xhr.responseType = 'json';
    xhr.onload = () => {
      const status = xhr.status;
      if (status === 200) {
        resolve(xhr.response);
      } else {
        reject(xhr.response);
      }
    };
    xhr.send();
  });
  return promise;
};

// promises is an array of Promise objects
// from the multiple getJSON calls
const promises = feedUrls.map((entry) => {
  return getJSON(entry)
    .then((data) => {
      arrayData.push(data[0]);
    })
    .catch((err) => {
      alert('Something went wrong: ' + err);
    });
});

// Promise.all's .then will only be done once all
// of the API calls are complete
Promise.all(promises).then(() => {
  // sort the data by id
  const sorted = arrayData.sort((a, b) => a.id - b.id);
  // build the li elements from the sorted data
  document.getElementById('listId1').innerHTML = sorted.map((x) => `<li>${x.id}</li>`).join("");
  document.getElementById('listId2').innerHTML = sorted.map((x) => `<li>${x.title}</li>`).join("");
});
ul {
  display: inline-block;
}
<ul id="listId1">empty</ul>
<ul id="listId2">empty</ul>

Related