How can I iterate trough a jQuery object and wait for an event?

Viewed 233

Could anyone explain how to wait for an event to finish to move forward on the next iteration inside a for loop? For example:

for ( i=0; i < item.length; i++ ) {
  $(video[i]).on('loadeddata', function() {
    // do my stuff
  })
}

Now, with this code I'm just adding an event listener to each video in the right order but obviously they will not load in this way. They will do my other stuff when they'll finish loading, and 'cause it's unpredictable, this ruins the order of the showed item on the page. So, how can I make the for loop to wait until the event finishes it's own stuff, before it goes to the next iteration?

EDIT: I'm adding more informations as requested. I actually have an object, containing HTML Elements. This object is retrieved with var items = $('.db-gallery-item'). The values of this object as I said are <div> elements, containing a container <div> with two elements inside for images (<img> and <a>) and a single <video> element for videos. Loading them i have to mantain their order, and so is required to wait for each element to load for make it visible and go working on the next.

SOLVED: Managed to solve this with the solution proposed by palash. This is the final code i used.

if(i_length > 0) {
    (async function() {
        for (i = 0; i < i_length; i++) {
            var item = $(items[i]);
            if($(item).find('.dbg-video').length == 1) {
                var video = $(item[0]).children().children();
                if(video[0].readyState > 3) {
                    item.removeClass('db-are-unloaded');
                    $grid.masonry('appended', $(item));
                }
                else {
                    await new Promise(resolve => video[0].addEventListener('loadeddata', resolve));
                    $(item[0]).removeClass('db-are-unloaded');
                    $grid.masonry('appended', $(item[0]));
                }
            } 
            else if($(item).find('.db-gallery-image').length == 1) {
                await new Promise(resolve => $(item[0]).imagesLoaded().done(resolve));
                $(item[0]).removeClass('db-are-unloaded');                                          
                $grid.masonry('appended', $(item[0]));
            }
        }
    }());
};

Here's a JSFiddle working example.

2 Answers

You can use for..await logic like:

(async function() {
   for (i = 0; i < video.length; i++) {
      await new Promise(resolve => video[i].addEventListener('loadeddata', resolve));
      // do my stuff
   }
}());

Here, for-loop iteration will wait until the loadeddata event on a video is finished.

DEMO:

const fruitsToGet = ['apple', 'grape', 'pear']
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const getNumFruit = (fruit, delay) => sleep(delay).then(v => fruit);

// Since getNumFruit returns a promise, we can await the resolved value before logging it.
const init = async () => {
  console.log('Start')

  for (let i = 0; i < fruitsToGet.length; i++) {
    const rand = Math.floor(Math.random() * 6) * 1000;
    const numFruit = await getNumFruit(fruitsToGet[i], rand)
    console.log(numFruit, 'delay: ' + rand)
  }

  console.log('End')
}
init()

You can create a function array, like:

var functions = [];
var index = -1;

function doMyStuff(currentIndex) {
    if (currentIndex = index + 1) {
        while ((index < functions.length) && functions[index]) {
            //Do something with $(video[index])
            index++;
        }
    }
}

for ( let j=0; j < video.length; j++ ) {
  $(video[j]).on('loadeddata', function() {
      functions[j] = true;
      doMyStuff(j);
  })
  functions[j] = false;
}

Explanation: We use a block-scoped variable for the loop so we will always know which video's event is fired. At the end of the loop functions will be an array, having only false elements (event did not occur yet) and upon each event, we first acknowledge which event occurred and then we call doMyStuff, which receives the index of the current element where the event has occurred. If that's the next in the queue, then its function is executed and all subsequent functions whose event already occurred.

A less custom way to do this is to apply a promise chain, see https://medium.com/@karenmarkosyan/how-to-manage-promises-into-dynamic-queue-with-vanilla-javascript-9d0d1f8d4df5

Related