如何遍历jQuery对象并等待事件?

3

有人能解释一下如何在for循环中等待事件完成才能继续下一次迭代吗? 例如:

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

现在,我只是给右侧显示的每个视频添加了一个事件监听器,但显然它们不会以这种方式加载。当它们完成加载并执行其它操作时,由于无法预测,这会破坏页面上显示项的顺序。那么,在for循环进入下一次迭代之前,如何使其等待事件完成自己的事情呢?
编辑:按要求添加更多信息。实际上我有一个包含HTML元素的对象。这个对象是用var items = $('.db-gallery-item')检索到的。正如我所说的,这个对象的值是<div>元素,包含一个容器<div>和两个内部元素的图像(<img><a>),以及单个的<video>元素。为了保持它们的顺序,必须等待每个元素加载后再进行可见性处理,并继续下一个。
解决:成功解决了这个问题,使用了palash提出的解决方案。这是我使用的最终代码。
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]));
            }
        }
    }());
};

这里是一个JSFiddle的工作示例。


for (i = 0; i < i.length; i++)将不起作用。你是不是想用video.length而不是i.length? - Lajos Arpad
@LajosArpad 对不起,我写那个是因为我想到我的对象不仅有视频还有图片,所以元素的数量是items.length。 - nico_b
能否提供有关视频和项目的更多信息?据我所知,项目是一个类似数组的对象,其中包含元素,而我认为视频是视频元素的数组。您能否进一步阐述以确保我们了解您的确切情况? - Lajos Arpad
2个回答

1
你可以使用像 for..await 这样的逻辑,例如:
(async function() {
   for (i = 0; i < video.length; i++) {
      await new Promise(resolve => video[i].addEventListener('loadeddata', resolve));
      // do my stuff
   }
}());

在这里,for循环迭代将等待video上的loadeddata事件完成。
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()


我认为问题在于每个视频加载的时间不同,但他希望回调按顺序调用。在您的示例中,可以使用sleep(random)而不是sleep(1200),但日志仍将按原始顺序显示。 - ariel
是的,我的问题就是这个。我想按顺序显示视频,但由于每个视频加载的时间不同,我不知道该怎么做。我会尝试您提出的解决方案,并告诉您结果。谢谢大家。 - nico_b
当然,请尝试提出的解决方案并查看其效果。 - palaѕн
@palaѕн,你的代码片段与响应中的代码行为不同。代码片段在每个回调上调用下一个加载器。而原始代码中,每个加载器同时被调用。 - ariel
@ariel 也注意到了。不过,我使用了回复中的代码,并在一些调整后完美地运行了。 - nico_b
显示剩余2条评论

0
你可以创建一个函数数组,例如:
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;
}

解释:我们在循环中使用块级作用域变量,以便始终知道哪个视频的事件被触发。在循环结束时,functions将是一个数组,仅具有false元素(事件尚未发生),并且在每个事件上,我们首先确认发生了哪个事件,然后调用doMyStuff,该函数接收事件发生的当前元素的索引。如果这是队列中的下一个元素,则执行其函数以及所有已经发生事件的后续函数。
一种不那么定制化的方法是应用Promise链,请参见https://medium.com/@karenmarkosyan/how-to-manage-promises-into-dynamic-queue-with-vanilla-javascript-9d0d1f8d4df5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接