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

в 15:54, , рубрики: deferred, each, javascript, jquery, Веб-разработка, метки: , , ,

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


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js