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 и их событий прогресса?
Ответы
Ответ 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/