Подтвердить что ты не робот

JQuery.when() прогресс для массива отложенных и/или обещаний

Я использую jQuery .when() to для переноса массива promises, чтобы я мог выполнить какое-либо действие, когда все 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, чтобы он мог вернуть общий прогресс без изменения внутри promises и их событий прогресса?

4b9b3361

Ответ 1

$.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 promises имеет механизм прогресса, и мы его не используем. Это означает, что уведомления о проделанной работе не настолько расширяемы, насколько это возможно.

К сожалению, поскольку $.when() возвращает обещание (а не отложенное), и вы не можете использовать .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() {
       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");
});

Аргумент против этой второй реализации заключается в том, что усилия по обещанию стандартов, похоже, отходят от прогресса, связанного с promises каким-либо образом (прогресс будет иметь отдельный механизм). Но теперь он в jQuery и, вероятно, будет долгое время (jQuery не соответствует стандартам обещания для письма), так что это действительно ваш выбор, куда идти.

Ответ 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);
});

Вы можете добавить обработку для фактических уведомлений jqXHR progress, используя третий аргумент .then. Вероятно, вам также необходимо убедиться, что результаты связаны с соответствующим запросом, что может потребовать дополнительного закрытия в обратном вызове.

Ответ 3

Сохраняя простые вещи, вы можете определить обобщенный классический конструктор.

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() );

Преимущество такого подхода заключается в повторном использовании. new Progress() может вызываться на любом количестве массивов promises, каждый со своим собственным обратным вызовом reporter.

Если бы вы захотели, Progress можно было бы сделать, чтобы вернуть обещанное обещание в стиле jfriend, хотя я бы не сделал этого так по причинам, которые уже дал jfriend.

И еще с мыслью Progress можно было бы вставить как плагин jQuery, позволяющий вызывать, например, следующим образом:

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

которые могут иметь преимущества при некоторых обстоятельствах.

Ответ 4

Try

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/