Search code examples
javascriptjquerypromisesynchronouschaining

Javascript Promise.all - how can I make this existing function synchronous?


I have a function which works fine when its ran synchronously, but as soon as I make it asynchronous it fails to work because it is returning true before all images are loaded.

Here is the original function which existed:

if (window.num_alt > 0) {
  var div = document.getElementById('productImageLarge');

  if (div) {
    var html = '';
    colour = colour || '';

    var tmp = getTemplate('image_holder');
    if (!tmp) { tmp = 'image_holder is missing<br>'; }

    // num_alt same number as images - use this for the loop
    for (var i=0; i<num_alt-0+1; i++) {
      var tmp1 = tmp;
      tmp1 = tmp1.replace(/\[xx[_ ]image\]/ig, imagename+colour+alt_ext[i]);
      tmp1 = tmp1.replace(/\[xx[_ ]img_no\]/ig, i);
      html += tmp1;

      // at the end of the loop
      if (i == num_alt) {
        imagesDone = true;
      }
    }
    div.innerHTML = html;
  }
}
return imagesDone;

Basically it takes the num_alt images set in a variable (set to 8) and fills in a JS template. Once its at the end of the loop I have another function on an interval testing whether imagesDone == true. Once it is set to true, the function fires and the image slider kicks in.

I wanted to lazy-load the images, and for some reason the current function wouldn't allow me to do this without trying to load images that return a 404. So I converted the function to use promises which calls itself until all images are processed (removed the for loop) and this has worked for a while, but its using async:false....

var imagesDone = false;
//console.log("Create Image");

if (window.num_alt > 0) {
  var div = document.getElementById('productImageLarge');

  if (div) {
    var html = '';
    colour = colour || '';

    var tmp = getTemplate('image_holder');
    if (!tmp) { tmp = 'image_holder is missing<br>'; }

    var i = 0;
    var promises = [];
    function ajax_data() {

      promises.push($.ajax({
        url: thisUrl+'product-image.php?size=large&image=/'+imagename+colour+alt_ext[i]+'.jpg',
        method: 'post',
        data: promises,
        async : false,
        success: function (resp) {
          if (i<=num_alt) {
            var tmp1;
            tmp1 = tmp;
            tmp1 = tmp1.replace(/\[xx[_ ]image\]/ig, imagename+colour+alt_ext[i]);
            tmp1 = tmp1.replace(/\[xx[_ ]img_no\]/ig, i);
            html += tmp1;
            div.innerHTML = html;
            i++;

            ajax_data();
          }
        }
      }))
    }

    Promise.all([ajax_data()])
      .then([imagesDone = true])
      .catch(e => console.log(e));

  }
}
return imagesDone;

If I remove the async:false, imagesDone is returned too soon and the slider function kicks in to early. Can anyone help me understand how to make this work in a synchronous / chained fashion? I've been trying for a while but just can't seem to get it to work.

Thanks in advance.


Solution

  • It's not clear what you want to do, your code looks like part of a function that already doesn't do what you want it to do. Maybe the following will work for you:

    var Fail = function(reason){this.reason=reason;};
    var isFail = function(o){return (o||o.constructor)===Fail;};
    var isNotFail = function(o){return !isFail(0);};
    
    //...your function returning a promise:
    var tmp = getTemplate('image_holder') || 'image_holder is missing<br>';
    var div = document.getElementById('productImageLarge');
    var html = '';
    var howManyTimes = (div)?Array.from(new Array(window.num_alt)):[];
    colour = colour || '';
    return Promise.all(//use Promise.all
      howManyTimes.map(
        function(dontCare,i){
          return Promise.resolve(//convert jQuery deferred to real/standard Promise
            $.ajax({
              url: thisUrl+'product-image.php?size=large&image=/'+imagename+colour+alt_ext[i]+'.jpg',
              method: 'post',
              data: noIdeaWhatYouWantToSendHere//I have no idea what data you want to send here
              // async : false //Are you kidding?
            })
          ).catch(
            function(error){return new Fail(error);}
          );
        }
      )
    ).then(
      function(results){
        console.log("ok, worked");
        return results.reduce(
          function(all,item,i){
            return (isFail(item))
              ? all+"<h1>Failed</h1>"//what if your post fails?
              : all+tmp.replace(/\[xx[_ ]image\]/ig, imagename + colour + alt_ext[i])
                .replace(/\[xx[_ ]img_no\]/ig, i);
          },
          ""
        );
      }
    ).then(
      function(html){
        div.innerHTML=html;
      }
    )