D. Lef
D. Lef

Reputation: 259

How to load contents of multiple files in particular order with Javascript/AJAX

I am using JS/AJAX to display the contents of multiple files on a page. The files are dynamically generated, and are loaded using a for-loop. There is one file that contains metadata for the set, including how many other files need to be loaded. The other files have a number in the filename, and I load them by requesting "file_[loop_index].json".

The problem is, the amount of data in each json file is arbitrary as well, and the AJAX calls will complete in the order of smallest file first. I want to display the data in order based on the name, rather than the size.

Here is a stripped-down example, suppose we want to display the contents of a book:

function get(url) {
  return new Promise(function(resolve, reject) {
    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      if (req.status == 200) {
        resolve(req.response);
      }
      else {
        reject(Error(req.statusText));
      }
    };

    req.onerror = function() {
      reject(Error("Network Error"));
    };

    req.send();
  });
}

get('toc.json').then(function(response) {
  chapters = JSON.parse(response)["chapters"];
  return chapters;
}).then(function(levels) {
  for (i = 0; i < chapters; i++) {
    (function (i) {
      get("chap".concat(i,".json")).then(function(response) {
      console.log(response);

  })
  }(i));
  }
  });

toc.json:

{ "chapters": 2 }

chap0.json:

{ "text" : "00000" }

chap1.json:

{ "text" : "1" }

The result will be:

{ "text" : "1" }
{ "text" : "00000" }

Where as if I were to switch the number of characters in each file, the result would be:

{ "text" : "0" }
{ "text" : "11111" }

I want to print the line with "0"'s before the line with "1"'s regardless of each file size.

I cannot use async/await, as my page must work in IE11 and it does not support arrow functions.

The solution I thought of was to store the contents in an array, return that from the for-loop, and display the contents, i.e.:

Attempted Solution:

var tempArray = [];

function get(url) {
  return new Promise(function(resolve, reject) {
    var req = new XMLHttpRequest();
    req.open('GET', url);

    req.onload = function() {
      if (req.status == 200) {
        resolve(req.response);
      } else {
        reject(Error(req.statusText));
      }
    };

    req.onerror = function() {
      reject(Error("Network Error"));
    };

    req.send();
  });
}

get('toc.json').then(function(response) {
  chapters = JSON.parse(response)["chapters"];
  return chapters;
}).then(function(levels) {
  for (i = 0; i < chapters; i++) {
    (function(i) {
      get("chap".concat(i, ".json")).then(function(response) {
        console.log(response);
        tempArray[i] = response;

      })
    }(i));
  }
  return tempArray;
}).then(function(response) {
    console.log(response);
  }

);

But the final console.log(response) yields:

Array[]

I suppose this fails because the promise sequence is proceeding past the for-loop section before the actual AJAX calls complete. If I could figure out how to make it wait for them to finish, I would be there, but at the moment I am stumped.

Another solution that could work under other circumstances is to dynamically generate part of the html, and update the correct elements with the corresponding file contents. Unfortunately this is not an option for my particular use-case.

Any help? Thank you.

Upvotes: 2

Views: 989

Answers (2)

mplungjan
mplungjan

Reputation: 177851

Bending over backwards to use promises. Why not just have the names in an array and have one function doing the getting, update the counter and call the function again?

var listOfFiles=["fileList.txt”],
    cnt=0, 
    oReq = new XMLHttpRequest();

function reqListener () {
  if (listOfFiles[0]=="fileList.txt") {
    listOfFiles = this.responseText.split(",");
  }
  else { 
    processFile(this.responseText);
    cnt++;
  }
  getFile();
}

oReq.addEventListener("load", reqListener);
function getFile() { 
  if (cnt >= listOfFiles.length) return; 
  oReq.open("GET", listOfFiles[cnt]);
  oReq.send(); 
} 
getFile();

Upvotes: 1

Paul Grime
Paul Grime

Reputation: 15104

Use Promise.all (XHR removed from the example, but Promises still used).

function getTOC() {
  var jsonStr = JSON.stringify({
    chapters: 2,
  });
  return Promise.resolve(jsonStr);
}

function getChapter(i) {
  return Promise.resolve("chapter " + i);
}

getTOC()
  .then(function(response) {
    var chapters = JSON.parse(response)["chapters"];
    return chapters;
  })
  .then(function(chapters) {
    var promises = [];
    for (i = 0; i < chapters; i++) {
      promises.push(getChapter(i));
    }
    return Promise.all(promises);
  })
  .then(function(results) {
    // results is an array of results, ordered as expected
    console.log(results);
  });

Upvotes: 1

Related Questions