0

I have a function that loads HTML from an external file via an AJAX call using jQuery.

These ajax call runs inside of a $.each loop and I need this ajax call to finish before the loop continues. Here is my code:

$('img').each(function(){
    var cotainer_html = $('.cotainer_html').clone();
    /* Get Image Content */
    $.ajax({
       url: '/assets/ajax/get_studio_image.php',
       type:'GET',
       success:function(data){
          cotainer_html.find('.replaceme').replaceWith(data);
       }
    });
});

I know I can set async:false but I hear that is not a good idea. Any ideas?

rrk
  • 15,677
  • 4
  • 29
  • 45
DigitalMC
  • 805
  • 2
  • 16
  • 31
  • async: false is the easiest way. You could also use this library: https://github.com/caolan/async – user2278120 Apr 21 '16 at 15:49
  • 3
    ***Never, ever use `async: false`*** It locks the UI thread of the browser until the request completes, which makes it look like it has crashed to the user. It is terrible practice. Use callbacks. – Rory McCrossan Apr 21 '16 at 15:49
  • @stackErr synchronous calls (outside of webworkers) are in the [process of being deprecated](https://xhr.spec.whatwg.org/#sync-warning) - some browsers are already logging warnings, the spec already allows for them to experiment with throwing errors - I wouldn't write any new code that uses it. – James Thorpe Apr 21 '16 at 15:49
  • Possible duplicate of [Wait until all jQuery Ajax requests are done?](http://stackoverflow.com/questions/3709597/wait-until-all-jquery-ajax-requests-are-done) – vesse Apr 21 '16 at 15:50
  • @JamesThorpe thanks! I didn't know that. Just deleted my answer. – stackErr Apr 21 '16 at 15:53
  • @Vesse thanks, your right this is pretty much the same question. I looked all over and missed that. My bad. – DigitalMC Apr 21 '16 at 16:07
  • Thanks for clarification around the async issue. I'm going to avoid it for sure. – DigitalMC Apr 21 '16 at 16:07

2 Answers2

2

To achieve this you can put each request in to an array and apply() that array to $.when. Try this:

var requests = [];
$('img').each(function(){
    var cotainer_html = $('.cotainer_html').clone();
    /* Get Image Content */
    requests.push($.ajax({
       url: '/assets/ajax/get_studio_image.php',
       type:'GET',
       success:function(data){
          cotainer_html.find('.replaceme').replaceWith(data);
       }
    }));
});

$.when.apply($, requests).done(function() {
    console.log('all requests complete');
});

Note that you're replacing the same content on each request, so the only one which will have any effect on the UI is the last request. The preceding ones are redundant.

Also note that you should never, ever use async: false. It locks the UI thread of the browser until the request completes, which makes it look like it has crashed to the user. It is terrible practice. Use callbacks.

The OP appears to want the calls to run in series, not in parallel

If this is the case you could use recursion:

function makeRequest($els, index) {
    var cotainer_html = $('.cotainer_html').clone();
    $.ajax({
       url: '/assets/ajax/get_studio_image.php',
       type:'GET',
       success:function(data){
          cotainer_html.find('.replaceme').replaceWith(data);
          if ($els.eq(index + 1).length) {
              makeRequest($els, ++index);
          } else {
              console.log('all requests complete');
          }
       }
    });
}

makeRequest($('img'), 0);
Rory McCrossan
  • 331,213
  • 40
  • 305
  • 339
2

You can use a pseudo-recursive loop:

var imgs = $('img').get();

var done = (function loop() {
    var img = imgs.shift();
    if (img) {
        var cotainer_html = $('.cotainer_html').clone();
        /* Get Image Content */
        return $.get('/assets/ajax/get_studio_image.php')
         .then(function(data) {
            cotainer_html.find('.replaceme').replaceWith(data);
        }).then(loop);
   } else {
       return $.Deferred().resolve();  // resolved when the loop terminates
   }
})();

This will take each element of the list, get the required image, and .then() start over until there's nothing left.

The immediately invoked function expression itself returns a Promise, so you can chain a .then() call to that that'll be invoked once the loop has completed:

done.then(function() {
    // continue your execution here 
    ...
});
Alnitak
  • 334,560
  • 70
  • 407
  • 495
  • This is pretty creative, I really like it a lot. Let me play around with it and I'll let you know how it turns out. – DigitalMC Apr 21 '16 at 16:12