В ходе разработки текущего энтерпрайз-проекта, понадобилось реализовать отложенную обработку коллекции элементов jQuery — имелся набор виджетов, содержимое которых нужно было загрузить по очереди, причем загрузка происходила асинхронно. Пришлось написать небольшое расширение к
Идея довольно простая — имеем коллекцию jQuery элементов, которую нужно обработать в цикле, при этом итерация должна происходить только после окончания обработки текущего элемента.
Прошу учесть, что предмет обсуждения предполагает знания работы с
Функция итерации должна возвращать
Небольшой copy/paste из кода
Еще пример
Такой механизм позволяет aсинхронно обрабатывать синхронные (статичные) коллекции.
Небольшой рабочий пример.
P.S. Может показаться странным таймаут внутри функции
Если не делать таймаут в моем коде, вызов следующей итерации попадет в обычный стек, и он вызовется раньше, чем
$.fn — eachDeferred.Идея довольно простая — имеем коллекцию jQuery элементов, которую нужно обработать в цикле, при этом итерация должна происходить только после окончания обработки текущего элемента.
Прошу учесть, что предмет обсуждения предполагает знания работы с
$.Deferred объектами jQuery.(function ($) {
$.fn.extend({
/*
* Iterates over jQuery object collection using deferred callbacks.
* the function assigned for iterator should return promise.
* resolved promises notify the main deferred, so we can track each loop result.
* returns promise.
*/
eachDeferred: function (c) {
var that = this,
dfd = $.Deferred(),
elms = $.makeArray(that),
i = elms.length,
next = function () {
setTimeout(function () { elms.length ? cb(elms.shift()) : dfd.resolve(that); }, 0);
},
cb = function (elm) {
$.when(c.call(elm, i - elms.length, $(elm))).done(function (result) {
dfd.notify(result);
next();
});
};
next();
return dfd.promise();
}
});
})(jQuery);
Функция итерации должна возвращать
$.Deferred объект, для определения окончания выполнения текущей итерации. Сам код плагина eachDeferred также возвращает $.Deferred объект, который можно использовать в цепочке вызовов. Объект оповещает о результатах каждой итерации через notify/progress, передавая результат итерации (arg, который можно вернуть из $.Deferred.resolve(arg) функции-обработчика цикла) в код функции-обработчика .progress(function(arg){}) (если имеется).Небольшой copy/paste из кода
that.widgets.eachDeferred(function (i, widget) {
return that.renderContent(widget);
}).done(dfd.resolve);
renderContent асинхронно вытягивает данные с сервера и отображает содержимое виджета. Визуально ощущается, что каждый виджет загружается в порядке очереди. В случае использования обычного механизма $.Deferred ($.get().success()) виджеты отображались бы рандомно — который скорее загрузится.Еще пример
var dfd = $.Deferred(), _r = [];
xml.children("Tree").eachDeferred(function (i, xmlNode) {
return that._buildTreeContents(xmlNode);
}).progress(function (treeContents) {
_r.push(treeContents);
}).done(function () {
dfd.resolve(_r);
});
return dfd.promise();
_buildTreeContents асинхронно строит разметку дерева, передавая нам результат каждой итерации (мы сохраняем его в массив). В конце обработки коллекции мы имеем общую разметку дерева, которую можем вставить в страницу.Такой механизм позволяет aсинхронно обрабатывать синхронные (статичные) коллекции.
Небольшой рабочий пример.
P.S. Может показаться странным таймаут внутри функции
next(). Таймаут нужен из-за кода jQuery — в нем вызов .notify() происходит через такой же таймаут. Сделано это для того, чтобы браузер мог завершить обработку текущего стека Javascript VM.Если не делать таймаут в моем коде, вызов следующей итерации попадет в обычный стек, и он вызовется раньше, чем
.notify() — нам же нужно сперва оповестить об окончании итерации, и после этого перейти к следующей.