В ходе разработки текущего энтерпрайз-проекта, понадобилось реализовать отложенную обработку коллекции элементов jQuery — имелся набор виджетов, содержимое которых нужно было загрузить по очереди, причем загрузка происходила асинхронно. Пришлось написать небольшое расширение к $.fn — eachDeferred.
Идея довольно простая — имеем коллекцию 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) в коде функции eachDeferred).
Небольшой copy/paste из кода
that.widgets.eachDeferred(function (i, widget) {
return that.renderContent(widget);
}).done(dfd.resolve);
renderContent
асинхронно вытягивает данные с сервера и отображает содержимое виджета. Визуально ощущается, что каждый виджет загружается в порядке очереди. В случае использования обычного механизма $.Deferred ($.get().sucess()) виджеты отображались бы рандомно — который скорее загрузится.
Еще пример
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синхронно обрабатывать синхронные (статичные) коллекции.
Автор: creage