0

I have the following function that calls an API several times:

function define(arr, callback) {
    var client = [];
    var definitions = {};
    for (var i = 0, len = arr.length; i < len; i++) {
        (function (i) {
            client[i] = new XMLHttpRequest();
            client[i].onreadystatechange = function () {
                if (client[i].readyState == 4 && client[i].status == 200) {
                    definitions[arr[i]] = client[i].responseText;
                }
            };
            client[i].open('GET', 'http://api.wordnik.com:80/v4/word.json/' + arr[i] +
                '/definitions?limit=200&includeRelated=false&sourceDictionaries=webster&useCanonical=false&includeTags=false&api_key=...',
                true);
            client[i].send();
        })(i);
    }
    return definitions;
}

As explained in How do I return the response from an asynchronous call? the return occurs before the async calls finish causing the function to return an empty object.

Because I have a loop executing many calls I cannot use the solution of status == 200 with a callback that returns what I want.

Things I've tried include making a global variable object and a callback that appends new key value pair to it every time it is called. This doesn't work for the same reason the original code doesn't.

I've also attempted to make a callback that returns the object and passing it to another function that doesn't make the call until a condition is met. This seems like the most promising path but I'm unsure of what condition I could use that would be true only after all async calls are finished as the callback would only have access to variables passed to it as arguments.

Community
  • 1
  • 1
Michael
  • 35
  • 5
  • Have a look at Promises and `Promise.all`, http://stackoverflow.com/documentation/javascript/231/promises/925/waiting-for-multiple-concurrent-promises#t=201607281713298352612 – Andreas Jul 28 '16 at 17:17

2 Answers2

0

You could use fetch and promises for this. Ofc for older browsers you would have to use some polyfill.

const urls = [
  'https://raw.githubusercontent.com/fizyk20/generic-array/master/README.md',
  'https://raw.githubusercontent.com/fizyk20/generic-array/master/src/iter.rs'
];

function getAll(urls) {
  const promises = urls.map(url => {
    return fetch(url)
      .then(response => response.text()); //or response.json() for json data and 
      //you can check response.ok property to see if response was successful
  });
  
  return Promise.all(promises);
}

getAll(urls)
  .then(txts => console.log(txts.slice(0, 400)));
sielakos
  • 2,406
  • 11
  • 13
0

You're almost there.
You only have to check, each time a request finishes/a returned definition is stored in definitions, if the number of results in definitions equals the number of entries in the array arr.
If all results have been stored in definitions call the callback and pass it the definitions

client[i].onreadystatechange = function () {
    if (client[i].readyState == 4 && client[i].status == 200) {
        definitions[arr[i]] = client[i].responseText;

        if (Object.keys(definitions).length === arr.length) {
            callback(definitions);
        }
    }
};

fiddle

Andreas
  • 21,535
  • 7
  • 47
  • 56
  • There is one problem with this, if ``client[i].readyState == 4 && client[i].status == 200`` ever one will be falsy, then callback will never be called. So there is need for failCallback or something like that. – sielakos Jul 28 '16 at 18:56