如何在for循环中使用Deferred对象

时间:2014-07-15 19:30:58

标签: javascript jquery asynchronous callback deferred

我不确定如何问这个。

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

有些东西告诉我Deferred是这样做的方式......我在其他情况下使用它但我不知道如何将各种deferred对象链接在一起,每个对应一个for循环的迭代,并为所有这些循环提供一个done函数(它是否有意义?我不知道我是否让自己清楚了。)

这基本上就是我正在尝试使用此代码,但自然这里延迟只会调用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对象中的数据。

2 个答案:

答案 0 :(得分: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);
});

答案 1 :(得分:2)

这是一段代码,它扩展了内置的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()方法,并且jQuery完成创建承诺的所有工作并跟踪何时当动画全部完成时,动画完成并解决承诺。我真的很惊讶jQuery不会自己这样做,因为它只是少量的额外代码,并且利用了他们已经在做的很多事情。

以下是此扩展程序的测试jsFiddle:http://jsfiddle.net/jfriend00/bAD56/

关于jQuery中内置.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);
    });
});;