【问题标题】:How to use Deferred objects in a for loop如何在 for 循环中使用延迟对象
【发布时间】:2014-07-15 19:30:57
【问题描述】:

我不知道该怎么问。

我想知道只有 URL 的三张图片的大小(不用说,这只是一个示例)。

有些东西告诉我 Deferred 是这样做的方法...我在其他情况下使用过它,但我不知道如何将各种 deferred 对象链接在一起,每个 @ 的迭代都有一个987654328@ 循环,并为所有这些设置一个 done 函数(这有意义吗?我不知道我是否说清楚了)。

这基本上就是我试图用这段代码做的事情,但自然在这里,deferred 只会调用一次done,而不是每张图片。我希望 Deferred 只有在我拥有所有图像的尺寸后才能得到解决。

更新

感谢jgreenbergBergi,我现在明白了,我需要为每张图像创建一个Deferred 对象,以及一个Promises 数组。新代码:

var imagesURLArray = [
  'http://placekitten.com/200/300',
  'http://placekitten.com/100/100',
  'http://placekitten.com/400/200'];

var promises = [];

function getImageDimensions(url){
  var deferred = new $.Deferred();
  var img = $('<img src="'+ imagesURLArray[i] +'"/>').load(function(){
    deferred.resolve( {width: this.width, height: this.height} );
  });
  return deferred.promise();
}

for (var i = 0; i < imagesURLArray.length; i++) {
  promises.push(getImageDimensions(imagesURLArray[i]));
}

$.when.apply($, promises).then(function(dimensions){
   console.log('dimensions: ' + dimensions);
});

Sample here

但是我仍然不知道如何从then() 中的所有 Deferred 对象中检索数据。 dimensions 参数仅返回来自第一个 Deferred 对象的数据。

【问题讨论】:

  • 看看 jQuery .when().
  • jQuery load 对于跨浏览器的图像不可靠
  • 您只能resolve 延迟一次,因此您需要为每张图像使用不同的延迟。如果您愿意,可以combine them with $.when
  • @update:请参阅this thread,了解如何访问合并延迟的结果。 (或者使用合适的 Promise 库)

标签: javascript jquery asynchronous callback deferred


【解决方案1】:

这里有一小段代码扩展了内置的 jQuery 承诺,适用于图像的 .load() 事件。这允许您执行一些非常简单的编码来等待您的图像组加载。这是 jQuery 的附加代码,用于支持图像加载的承诺:

(function() {
    // hook up a dummy animation that completes when the image finishes loading
    // If you call this before doing an animation, then animations will
    // wait for the image to load before starting
    // This does nothing for elements in the collection that are not images
    // It can be called multiple times with no additional side effects
    var waitingKey = "_waitingForLoad";
    var doneEvents = "load._waitForLoad error._waitForLoad abort._waitForLoad";
        jQuery.fn.waitForLoad = function() {
        return this.each(function() {
            var self = $(this);
            if (this.tagName && this.tagName.toUpperCase() === "IMG" && 
              !this.complete && !self.data(waitingKey)) {
                self.queue(function(next) {
                    // nothing to do here as this is just a sentinel that 
                    // triggers the start of the fx queue
                    // it will get called immediately and will put the queue "inprogress"
                }).on(doneEvents, function() {
                    // remove flag that we're waiting,
                    // remove event handlers
                    // and finish the pseudo animation
                    self.removeData(waitingKey).off(doneEvents).dequeue();
                }).data(waitingKey, true);
            }
        });
    };

    // replace existing promise function with one that
    // starts a pseudo-animation for any image that is in the process of loading
    // so the .promise() method will work for loading images
    var oldPromise = jQuery.fn.promise;
    jQuery.fn.promise = function() {
        this.waitForLoad();
        return oldPromise.apply(this, arguments);
    };
})();

这通过覆盖当前的.promise() 方法来工作,当为集合中尚未完成加载的每个图像调用.promise() 方法时,它会在jQuery“fx”队列中启动一个虚拟动画,并且那个虚拟当图像的“加载”事件触发时动画完成。因为 jQuery 已经支持动画的 Promise,在为每个尚未加载的图像启动虚拟动画之后,它可以只调用原始的 .promise() 方法,jQuery 会完成创建 Promise 并跟踪动画时间的所有工作完成并在动画全部完成时解决承诺。实际上,我很惊讶 jQuery 自己没有这样做,因为它的附加代码量如此之少,并且利用了他们已经在做的很多事情。

这是此扩展的测试 jsFiddle:http://jsfiddle.net/jfriend00/bAD56/

关于 jQuery 中内置的 .promise() 方法的一个非常好的事情是,如果您在对象集合上调用它,它将返回给您一个主 Promise,该主 Promise 只有在所有单独的 Promise 都已解决后才会解决,所以你不必做所有的家务——它会为你做那些肮脏的工作。


.promise() 的覆盖是否是一个好方法,这是一个见仁见智的问题。对我来说,为现有的.promise() 方法添加一些额外的功能似乎很好,这样除了动画之外,它还可以让您管理图像load 事件。但是,如果该设计选择不是您喜欢的,并且您宁愿保留现有的.promise() 方法,那么可以将图像加载承诺行为放在新方法.loadPromise() 上。要以这种方式使用它,而不是以分配 oldPromise = ... 开头的代码块,您可以替换为:

jQuery.fn.loadPromise = function() {
    return this.waitForLoad().promise();
};

而且,要检索包含图像load 事件的承诺事件,您只需调用obj.loadPromise() 而不是obj.promise()。本文中的其余文本和示例假定您使用的是.promise() 覆盖。如果您切换到.loadPromise(),则必须在剩余的文本/演示中替换它。


这个扩展中的概念也可以用于 DOM 中的图像,因为您可以执行类似的操作,这样您就可以在 DOM 中的一组图像加载后立即执行某些操作(无需等待所有图像待加载):

$(document).ready(function() {
    $(".thumbnail").promise().done(function() {
        // all .thumbnail images are now loaded
    });
});

或者,与原始问题无关,但您可以使用此扩展程序做一些有用的事情是您可以等待每个单独的图像加载,然后在加载后立即在每个图像上启动动画:

$(document).ready(function() {
    $(".thumbnail").waitForLoad().fadeIn(2000);
});

每张图片都会在加载完成后开始淡入淡出(如果已经加载,则立即淡入淡出)。


而且,使用上述扩展程序的代码如下所示:

var imagesURLArray = [
  'http://placekitten.com/200/300',
  'http://placekitten.com/100/100',
  'http://placekitten.com/400/200'];

// build a jQuery collection that has all your images in it
var images = $();
$.each(imagesURLArray, function(index, value) {
    images = images.add($("<img>").attr("src", value));
});
images.promise().done(function() {
    // all images are done loading now
    images.each(function() {
        console.log(this.height + ", " + this.width);
    });
});

工作 jsFiddle 演示:http://jsfiddle.net/jfriend00/9Pd32/


或者,在现代浏览器(支持数组上的.reduce())中,您可以使用这个:

imagesURLArray.reduce(function(collection, item) {
    return collection.add($("<img>").attr("src", item));
}, $()).promise().done(function() {
    // all images are done loading now
    images.each(function() {
        console.log(this.height + ", " + this.width);
    });
});;

【讨论】:

  • 努力+1,即使我反对覆盖$.fn.promise
  • @Bergi - 按照我的编写方式,可以删除.promise() 的替换,并需要在调用.promise() 之前手动调用.waitForLoad(),如.waitForLoad().promise(),但我通过将其构建到.promise() 中来实现更内置的行为,并且对于集合中在加载过程中不是图像的任何对象都是无操作的。只是好奇,为什么反对更换$.fn.promise?它只是进行一次方法调用,然后逐字调用并返回原始方法。
  • 它只是不适合那里恕我直言。我宁愿让.waitForLoad() 通过将.promise() 附加到其中的.each(…) 调用来返回一个承诺:-)
  • @Bergi - .promise() 适用于 jQuery 中的动画和任何其他排队动作。为什么它也不能用于图像加载?它的 jQuery 描述是这样的:Return a Promise object to observe when all actions of a certain type bound to the collection, queued or not, have finished. 这对我来说似乎很合适。它还有一个非常好的特性,当你在一个集合上调用它时,它会返回一个承诺,当集合的所有单个项目都解决时,它会解决,这节省了手动 $.when() 编码,我也想利用它。
  • @Bergi - 我不想从.waitForLoad() 返回一个承诺,因为通过像这样$(imageSelector).waitForLoad().animate(...) 不带甚至涉及承诺。
【解决方案2】:

你需要使用http://api.jquery.com/jquery.when/

我试图修改您的代码,但我没有运行它。我相信你会从这里弄清楚。

function getImageDimensions(url){
  var deferred = new $.Deferred();
  var img = $('<img src="'+ imagesURLArray[i] +'"/>').load(function(){
    var dimensions = [this.width, this.height];
    deferred.resolve(dimensions);
  });
  return deferred.promise()
}

var promises = []
for (var i = 0; i < imagesURLArray.length; i++) {
  promises.push(getImageDimensions(imagesURLArray[i]));
}
$.when.apply($, promises).then(function(dimensions){
   console.log('dimensions: ' + dimensions);
});

【讨论】:

  • 谢谢,虽然我仍然只得到第一个文件的尺寸,但它似乎非常接近。我设法用一个不太优雅的解决方案来完成here
  • 我会将图像数组转换为图像映射{url:width},而不是将值传递回 deferred 。我从来没有像这样延迟传回数据。
  • @jgreenebrg:事实上,您应该始终将数据传回deferred.resolve。即使 jQuery 从 $.when 传递 usable results 失败
  • @Bergi 有道理,我从来没有理由这样做。对我来说,您无法访问 when 的结果是有道理的。
猜你喜欢
  • 2019-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多