jQuery.when()Deferred和/或Promise数组的进度

时间:2014-09-26 18:36:20

标签: javascript jquery promise jquery-deferred

我正在使用jQuery' .when()wrap an array of promises,以便在所有承诺都得到解决后我可以采取一些行动。

$.when.apply($, requests).done(function () {
    console.log(arguments); //it is an array like object which can be looped
    var total = 0;
    $.each(arguments, function (i, data) {
        console.log(data); //data is the value returned by each of the ajax requests

        total += data[0]; //if the result of the ajax request is a int value then
    });

    console.log(total)
});

假设我希望在每个承诺得到解决时得到通知,作为展示进展的一种方式。例如,如果requests有50个请求并且其中3个已被解析,我希望能够以6%显示进度条。有没有办法使用$.when,以便它可以返回整体进度,而无需修改内部承诺及其进度事件?

4 个答案:

答案 0 :(得分:11)

$.when()不会为您执行进度通知。注册每个承诺的进度通知,或者您可以制作自己的$.when()版本,首先在每个承诺上注册完成通知然后调用$.when()

$.whenWithProgress = function(arrayOfPromises, progessCallback) {
   var cntr = 0;
   for (var i = 0; i < arrayOfPromises.length; i++) {
       arrayOfPromises[i].done(function() {
           progressCallback(++cntr, arrayOfPromises.length);
       });
   }
   return jQuery.when.apply(jQuery, arrayOfPromises);
}

$.whenWithProgress(requests, function(cnt, total) {
    console.log("promise " + cnt + " of " + total + " finished");
}).then(function() {
    // done handler here
}, function() {
    // err handler here
});

我一直在考虑这个问题,而且我有点想要进度通知,jQuery承诺有进步机制而我们没有使用它。这意味着进度通知不像它们那样可扩展。

不幸的是,因为$.when()返回一个promise(不是延迟的)而你不能在一个承诺上使用.notify()来触发进度通知,所以上面的代码是最简单的方法。但是,为了使用.progress通知而不是自定义回调,可以这样做:

$.whenWithProgress = function(arrayOfPromises) {
   var cntr = 0, defer = $.Deferred();
   for (var i = 0; i < arrayOfPromises.length; i++) {
       arrayOfPromises[i].done(function() {
           defer.notify(++cntr, arrayOfPromises.length);
       });
   }
   // It is kind of an anti-pattern to use our own deferred and 
   // then just resolve it when the promise is resolved
   // But, we can only call .notify() on a defer so if we want to use that, 
   // we are forced to make our own deferred
   jQuery.when.apply(jQuery, arrayOfPromises).done(function() {
       defer.resolveWith(null, arguments);
   });
   return defer.promise();
}

$.whenWithProgress(requests).then(function() {
    // done handler here
}, function() {
    // err handler here
}, function(cnt, total) {
    // progress handler here
    console.log("promise " + cnt + " of " + total + " finished");
});

反对第二个实施的论点是,承诺标准的努力似乎正在逐渐取消以任何方式与承诺相关的进展(进展将有一个单独的机制)。但是,它现在在jQuery中并且可能会持续很长时间(jQuery不符合信函的承诺标准)所以它真的是你选择的方式。

答案 1 :(得分:2)

我认为你不能(或应该)使用$.when执行此操作,其回调仅用于调用一次 - 您需要一个可以多次调用的回调,因此您可以将其传递给requests中的每个承诺。例如:

var count = requests.length;
var complete = 0;

function progress(response) {
    complete += 1;
    showProgress(complete / count);
    saveTheResponseSomewhere(response);

    if (complete === count) {
        doSomeAllDoneAction();
    }
}

requests.forEach(function(request) {
    request.then(progress);
});

您可以使用progress的第三个参数为实际的jqXHR .then通知添加处理。您可能还需要确保结果与相应的请求相关联,这可能涉及回调中的额外闭包。

答案 2 :(得分:2)

保持简单,你可以定义一个通用的经典构造函数。

var Progress = function(promiseArray, reporter) {
    this.promiseArray = promiseArray;
    this.reporter = reporter || function(){};
    this.complete = 0;
    $.each(promiseArray, function(i, p) {
        p.then(this.increment).then(this.report);
    });
};
Progress.prototype.increment = function() {
    this.complete += 1;
};
Progress.prototype.report = function() {
    return this.reporter(this.complete, this.promiseArray.length);
};
Progress.prototype.get = function() {
    return { complete:this.complete , total:this.promiseArray.length};
};

然后,例如进度温度计:

var progObj = new Progress(requests, function(complete, total) {
    var scale = 150;
    $("selector").css('someProperty', (scale * complete / total) + 'px');
});

或者进行临时调查:

console.log( progObj.get() );

这种方法的好处是可重用性。可以在任意数量的promise数组上调用new Progress(),每个数组都有自己的reporter回调。

如果你愿意的话,可以让Progress返回一个jfriend风格的通知承诺,虽然我不会这样做,因为jfriend已经给出了。

有一些更多的想法Progress可以被定义为jQuery插件,允许你调用,例如,如下:

$("selector").progress(function(complete, total) {
    var scale = 150;
    $(this).css('someProperty', (scale * complete / total) + 'px');
});

在某些情况下可能有优势。

答案 3 :(得分:1)

尝试

HTML

<progress id="progress" min="0" max="100"></progress>
<label for="progress"></label> 

JS

$(function () {
    var arrayOfPromises = $.map(new Array(50), function (v, k) {
        return v === undefined ? new $.Deferred(function (dfd) {
            $.post("/echo/json/", {
                json: JSON.stringify(k)
            }).done(function (data, textStatus, jqxhr) {
                return dfd.notify(k).resolve([data, textStatus, jqxhr])
            });
            return dfd.promise()
        }) : null
    }),
        res = [],
        count = null;

    $.each(arrayOfPromises, function (k, v) {
        $.when(v)
            .then(function (p) {
            console.log(p[1]);
            res.push(p);
            if (res.length === arrayOfPromises.length) {
                console.log(res);
                $("label").append(" done!");
            }
        }, function (jqxhr, textStatus, errorThrown) {
            res.push([textStatus, errorThrown, count])
        }, function (msg) {
            ++count;
            count = count;
            console.log(msg, count);
            $("progress").val(count * 2).next().text(count * 2 + "%");
        })
    })
})

jsfiddle http://jsfiddle.net/guest271314/0kyrdtng/


以前使用替代方法的努力:

HTML

<progress id="progress" value="0" max="100"></progress>
<output for="progress"></output>

JS

$(function () {
    $.ajaxSetup({
        beforeSend: function (jqxhr, settings) {
            var prog = $("#progress");
            jqxhr.dfd = new $.Deferred();
            jqxhr.dfd.progress(function (data, _state) {
                prog.val(data)                    
                .siblings("output[for=progress]")
                .text(prog.val() + "% " + _state);   
                if (_state === ("resolved" || "rejected")) {
                    prog.val(100);
                    window.clearInterval(s);
                };
            });
            var count = 0.000001;
            jqxhr.counter = function (j) {
                this.dfd.notify(Math.ceil(count), this.state());
                ++count;
                console.log(this.state(), prog.prop("value"));
            };
            var s = setInterval($.proxy(jqxhr.counter, jqxhr, jqxhr), 15);
        }
    });

    $.post("/echo/json/", {
        json: JSON.stringify({
            "defer": new Array(10000)
        })
    })
    .always(function (data, textStatus, jqxhr) {
        console.log(data, jqxhr.state(), $("#progress").val());
    });
})

jsfiddle http://jsfiddle.net/guest271314/N6EgU/