Pull to refresh

eachDeferred — отложенная обработка коллекции, one by one

Reading time2 min
Views2K
В ходе разработки текущего энтерпрайз-проекта, понадобилось реализовать отложенную обработку коллекции элементов jQuery — имелся набор виджетов, содержимое которых нужно было загрузить по очереди, причем загрузка происходила асинхронно. Пришлось написать небольшое расширение к $.fneachDeferred.

Идея довольно простая — имеем коллекцию 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() — нам же нужно сперва оповестить об окончании итерации, и после этого перейти к следующей.
Tags:
Hubs:
+3
Comments7

Articles

Change theme settings