Search code examples
javascriptjqueryloopsforeachtwitch

For each not firing in the right order


Good evening.

I'm currently working on an app that uses the twitch API.

For the first time I had to use the forEach JS command. But for some reason I cannot figure out, it seems kind of messy, as if for each wasn't firing in the right order, and as if the function is sometimes executed several time before the next array's element is fired.

I built a codepen that set the problem apart :

https://codepen.io/Hadrienallemon/pen/bLZJeX

As you can see in the pen, if you click several time on the test button, the result aren't always in the right order.

Here's the code :

HTML :

<button id="button">test button</button>
<div class="wrapper"></div>

CSS :

html,body{
  height : 100%;
  width : 100%;
}

.wrapper{
  height : 90%;
  width : 100%;
}

.awnser{
  background-color : tomato;
  margin : 50px auto;
  width : 60%;
  min-height : 10%;

}

JS :

var lives = ["nat_ali","krayn_live","streamerhouse","merry"];
$("button").on("click",function(){ 
  lives.forEach(function(element){
    $(".wrapper").empty();
      $.getJSON("https://wind-bow.glitch.me/twitch-api/streams/"+ element +"?callback=?",function(quote){

        if (quote.stream != null){
          $(".wrapper").append("
          <div class = 'awnser'>
              <p>"+quote.stream.game+"</p>
          </div>");
        }
        else{
          $(".wrapper").append("
          <div class = 'awnser'>
            <span class = 'circle' style ='text-align : right'>
              <p style = 'display : inline-block;'>offline</p>
            </span>
          </div>");
       }

    })
      $.getJSON("https://wind-bow.glitch.me/twitch-api/users/"+ element +"?callback=?",function(quote){

        console.log(quote.logo);
        $(".awnser:last-child").append("
        <div style ='width : 10%; height : 10%;'>"+ quote.display_name +"
            <img src = '"+quote.logo+"' style = 'max-width : 100%;max-height : 100%;'></div>");

    }) 
  })  
})

Solution

  • $.getJSON is asynchronous. Your forEach starts the calls in a given order, but they can complete in any order at all, completely chaotically.

    If you want to process their completions in order, you can save each promise from $.getJSON in an array, wait for them to complete with $.when (they'll run in parallel, but your callback won't run until they're all done), and then process the results:

    $.when.apply($, lives.map(element => $.getJSON(/*...*/))
    .done((...results) => {
        // All calls are done, process results
        results.forEach(result => {
            // ...
        });
    });
    

    jQuery's $.when will call your done callback with an argument for each of the promises you pass it. In the above, we gather those up in an array via a rest parameter and then loop through it.

    Or with pre-ES2015 syntax using the arguments pseudo-array:

    $.when.apply($, lives.map(function(element) { return $.getJSON(/*...*/)})
    .done(function() => {
        var results = Array.prototype.slice.call(arguments);
        // All calls are done, process results
        results.forEach(function(result) {
            // ...
        });
    });