В ходе разработки текущего энтерпрайз-проекта, понадобилось реализовать отложенную обработку коллекции элементов 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() — нам же нужно сперва оповестить об окончании итерации, и после этого перейти к следующей.