2

I am attempting to create a viewer which used the twitch.tv api to check an array of twitch streamers and then post which users are currently online and what they are streaming. I am having some serious trouble figuring out how to work with the asynchronous nature of callbacks.

I want to loop though the api call once for each twitch user in the array. AFTER they have all returned I want to be able to sort through them and list them in order of specific criteria.

After much research I am trying to use promises to do this. However I still cannot manage to figure this out.

I have used this question: How can I wait for set of asynchronous callback functions? and this question: How do I return the response from an asynchronous call? as a guideline but obviously I am still missing something important. I have posted the relevant part of my code. I would appreciate any help/ nudge in the right direction to what I am missing.

 $(document).ready(function() {
var channels = ["freecodecamp", "storbeck", "terakilobyte", "habathcx","RobotCaleb","thomasballinger","noobs2ninjas","beohoff","ESL_SC2", "brunofin"];
var test = [];
function getData(){
    var promises = [];
    for (var i = 0; i < channels.length; i++) {
        promises.push($.getJSON('https://api.twitch.tv/kraken/streams/'+channels[i]+'?callback=?', function(data) {}));
    }
    $.when.apply($, promises).then(function() {
        // returned data is in arguments[0][0], arguments[1][0], ... arguments[9][0]
        // you can process it here
        for(var j=0; j<promises.length; j++){
            //console.log(promises[j].responseJSON);
            test.push(promises[j]);
        }
    }, function() {
        // error occurred
        conole.log("get data error");
    });
}

    getData();
    console.log("do I have data?: ");
    for(var j=0; j<test.length; j++){
            console.log(test[j].responseJSON); //This is empty and where I need the data!
        }

});

MichelleJS
  • 759
  • 3
  • 16
  • 32
  • You should `return` the `$.when.apply($, promises)` from your function, and then put the `.then(…)` after the `getData()` call – Bergi Apr 19 '16 at 18:22

2 Answers2

2

In your code, you're calling getData() and then on the next line you iterate over the test array. This happens in the same pass of the event loop so you're not giving your promises any chance to actually run their logic and resolve to anything. Line-by-line execution is synchronous and no operations can get executed in between.

A solution is to return a promise from the getData function and wait for it to resolve.

Since you're working with Promises you also don't need any callback to the $getJSON function.

$(document).ready(function() {
  var channels = [
    "freecodecamp", "storbeck", "terakilobyte", "habathcx",
    "RobotCaleb", "thomasballinger", "noobs2ninjas", "beohoff",
    "ESL_SC2", "brunofin"
  ];

  var test = [];

  function getData() {
    var promises = [];
    for (var i = 0; i < channels.length; i++) {
      promises.push(
        $.getJSON(
          'https://api.twitch.tv/kraken/streams/'+channels[i]+'?callback=?'
        )
      );
    }

    return $.when.apply($, promises).then(function() {
      for(var j=0; j<promises.length; j++){
        test.push(promises[j]);
      }
    });
  }

  getData().then(function() {
    console.log("do I have data?: ");
    for(var j=0; j<test.length; j++) {
       console.log(test[j].responseJSON);
     }
  });
});

Let me also suggest a refactoring of this code. This is an interesting question and a common thing to do in JavaScript and I'd like demonstrate how to work with arrays of promises using this example.

Each asynchronous operation runs independently and your instinct was correct: you need some additional tool to put all the results together.

When using promises, this tool is Promise.all. In fact, jQuery's $.when is similar although I find Promise.all easier to work with due to its inputs and return types. It's also standard JavaScript and is available in all modern browsers.

Promise.all can be used to run some logic when a number of individual promises resolve. Formally, Promise.all takes an array of promises and returns a single promise which resolves when all of the input promises resolve (or one of them rejects). it resolves to an array of resolved values to which individual promises resolved to.

Since Promise.all takes an array, it works great with array methods like map and filter.

function getStreams(channels) {
  // Transform an array of channel names into an array of Promises
  // representing the asynchronous action of getting the info
  // about each channel.
  const promises = channels.map(
    channel => $.getJSON(
      'https://api.twitch.tv/kraken/streams/'+channel+'?callback=?'
    )
  );

  // Return a new promise which will resolve when *all* channels
  // have been queried.
  return Promise.all(promises);
}

const channels = [
  "freecodecamp", "storbeck", "terakilobyte", "habathcx",
  "RobotCaleb", "thomasballinger", "noobs2ninjas", "beohoff",
  "ESL_SC2", "brunofin"
];

getStreams(channels).then(
  // infos is an array of resolved data about the channels
  infos => infos.forEach(
    info => console.log(info)
  )
);
  • This is perfect. It is so much cleaner and more readable then trying to fit it all into one giant function. – MichelleJS Apr 19 '16 at 23:51
0

Asynchronous is exactly what the name implies.

You can only have access to that data in the first function of the when callbacks. due to the fact that the data does not exist on your page until then.

Tuvia
  • 859
  • 4
  • 15