Javascript-форум (https://javascript.ru/forum/)
-   Ваши сайты и скрипты (https://javascript.ru/forum/project/)
-   -   Упрощенный Deferred (https://javascript.ru/forum/project/22357-uproshhennyjj-deferred.html)

Riim 16.10.2011 18:28

Упрощенный Deferred
 
Примерно тоже самое находиться в jQuery и Dojo, но здесь сильно упрощенный вариант (не продумана вероятность ошибок в обработчиках, нельзя указать контекст выполнения обработчиков и т. д.). Предназначен, чтобы разобраться, как это работает на простом примере и/или для допиливания (выкладывайте прямо здесь, что получается). Критика приветствуется.

/**
 * Id: deferred
 * */

(function() {

var arraySlice = [].slice;

function Deferred() {
	if (!(this instanceof Deferred)) {
		throw new TypeError();
	}
	this._then = [];
	this.then.apply(this, arguments);
}

Deferred.prototype = {
	_resolved: false,

	then: function _(callback) {
		if (callback) {
			if (this._resolved) {
				callback();
			} else {
				this._then.push(callback);
			}
		}
		if (arguments.length > 1) {
			_.apply(this, arraySlice.call(arguments, 1));
		}
		return this;
	},

	resolve: function() {
		if (!this._resolved) {
			this._resolved = true;
			var results = [], then = this._then, i = 0, l = then.length;
			while (i < l) {
				results[i] = then[i++].apply(null, arguments);
			}
			return results;
		}
	}
};

Deferred.when = function() {
	var dfr = new Deferred(), count = 0;
	function resolveFunc() {
		if (!--count) {
			dfr.resolve();
		}
	}
	for (var i = 0, l = arguments.length; i < l; ++i) {
		if (arguments[i]) {
			++count;
			arguments[i].then(resolveFunc);
		}
	}
	return dfr;
};

this.Deferred = Deferred;

})();

// пример использования:
var dfr1 = new Deferred(function() { console.log('dfr 1'); });
var dfr2 = new Deferred(function() { console.log('dfr 2'); });

Deferred.when(dfr1, dfr2).then(function() { console.log('dfr all'); });

setTimeout(function() { dfr1.resolve(); }, 2000);
setTimeout(function() { dfr2.resolve(); }, 5000);


UPD: полезные ссылки: 1, 2, 3, 4, 5.

tenshi 17.10.2011 10:22

а слабо разобраться в этой штуке? ;-) http://forum.mozilla-russia.org/viewtopic.php?id=51812

Riim 17.10.2011 15:23

Цитата:

Сообщение от tenshi
а слабо разобраться в этой штуке?

это что-то вроде этого: http://softwaremaniacs.org/blog/2009/12/11/adisp/ ?

tenshi 18.10.2011 00:48

угу, только с поддержкой исключений и на яваскрипте

float 29.10.2011 03:23

для своего велосипеда сделал диферд следующей структуры:

var d = _.deferred() - создаёт объект. в моей реализации это типо суперкласс.
d.stage('ajax') - создаёт стадию.
таких стадий может быть сколько угодно.
их можно:
d.resolve('ajax')
d.reject('ajax')
d.stage('ajax').done(function() {...}).fail(function() {...});
ещё может передаваться контекст и параметры, можно выполнять без имени, тогда завершиться следующая стадия в очереди.
если в d не осталось незавершённых стадий, он сам завершается, тут 3 варианта:
success (все стадии resolve)
err (хоть одна reject)
anyway

вроде всё...

Как задумка?:)
сам как написал, сел задумался: какая же х*рня. какое-то масло масляное(у меня ещё when есть). и тд и тп.
но решил оставить.
уже пару раз, с удивлением, словил себя на мысли, когда юзал его, мол "вау. так пригодилось, а я о таком даже не подумал".
но чёт до сих пор сомневаюсь...

Gvozd 29.10.2011 05:01

я правильно понял, что на каждую созданную стадию, вы можете навешивать свои собственные обработчики, и вызывать их обработку для конкретной стадии?
По-сути, можно заменить массивом независимых Deferred, каждый из которых представляет свое состояние?

Если да, то в чем смысл/польза?
Если нет, то прошу пояснить подробнее идею. желательно с примерами, где она гармонично у вас вписывается

float 29.10.2011 13:52

Да, правильно.
Цитата:

По-сути, можно заменить массивом независимых Deferred
да можно.

Смысл/польза в том, что свой велосипед юзаю в риа. и там случаев, где надо делать массив больше, нежели где надо простой диферд.

Код писал под пример(тоесть сначала написал пример, а потом код для реализации).
Тут могут быть эм-ты фреймворка если что.

var d = _.deferred();

// ##### --- stages actions

d.stage('animation').done(function() {
		console.log('animation finished');
	}).fail(function() {
		console.log('animation failed');
	});

d.stage('ajax').done(function() {
		console.log('ajax done');
	}).fail(function() {
		console.log('ajax fail');
	});

d.stage('button');

// ##### --- actions

_.post('some/url.php').succes(function(data) {
	d.resolve('ajax', data);
}).err(function(err) {
	d.reject('ajax', err);
});

_('.menu').hide(200, function() {
	if(this.length) {
		d.resolve('animation');
	}
	else {
		d.reject('animation');
	}
});

_('.button-forward').click(function() {
	d.resolve('button');
});
_('.button-back').click(function() {
	d.reject('button');
});

// ##### --- defered finally

d.success(function() {
	console.log('chain success');
}).anyway(function() {
	console.log('chain done');
}).err(function() {
	console.log('chain with err');
});


Вот. Я было попробовал это переписать короче - не получилось...
Ещё из плюсов:
не засирается область лишними переменными.

+я изначально диферд сделал простым. тоесть все свойства создаются в момент навешивания обработчиков.
так что можно не париться что колбасу режем скальпелем.

Gvozd 29.10.2011 14:13

судя по последним строчкам вы сделали DeferredList, который позволяет обращаться через себя к отдельным Deferred
Плюс добавление наблюдаемых объектов на лету.

Вопрос на засыпку: что будет, если я после d.success() добавлю, а позже выполню еще одну стадию?
сработает ли еще раз d.success с дополниельной стадией?

float 29.10.2011 14:26

Цитата:

с дополниельной стадией?
ща проверил. нет не срабатывает.
то есть после выполнения диферда на него можно навешать ещё стадий. и выполнять их. но обраьботчики на дифферде сработают только 1 раз.
признаться про это я не думал, когда делал(ну или мб забыл, ночь всё таки была:)).

Цитата:

DeferredList
ну не совсем.
я забыл упомянуть, что и у самого диферда есть d.finish().
тоесть его самого можно юзать как простейший $диферд.
d.anyway(function() {...}).anyway(function() {...}).anyway(function() {...});
d.finish();

В общем АД:D

Gvozd 29.10.2011 14:30

Цитата:

Сообщение от float
нет не срабатывает.

ну, значит нормально

Цитата:

Сообщение от float
я забыл упомянуть, что и у самого диферда есть d.finish().

что он делает?
resolve-ит все объявленные стадии и глобальный обработчик?

float 29.10.2011 14:34

Цитата:

resolve-ит все объявленные стадии и глобальный обработчик?
нет не ресолвит. просто принудительно вызывает глобальные обработчики. это сразу планировался как внутренний метод. он вызывается когда все стадии выполнены.
и тут есть запарка, когда его вызывают из вне.
при таком вызове точно не ясно что делать с обработчиками success и err...
сейчас если диферд без стадий вызывается success(тк reject-а не было) и anyway

Gvozd 29.10.2011 14:56

Цитата:

Сообщение от float
это сразу планировался как внутренний метод.

мне кажется и не стоит его выносить.
Ведь, тогда вы обманываете обработчики, говоря им, что событие уже выполнилось, но результатов события получить при этом нельзя

float 29.10.2011 15:18

Цитата:

тогда вы обманываете обработчики
да. с этой позиции вы правы.
но есть другой момент.
у диферда вообще может не быть стадий. а success err anyway можно вешать.
логично иметь метод чтобы их вызвать...

float 29.10.2011 17:33

только я думаю надо чуть изменить.
finish зарыть.
а success err anyway можно сделать разное поведение в зависимости от аргументов.
функция - добавление в очередь. иначе вызов обработчиков в контексте с параметрами.

x-yuri 30.10.2011 03:52

float, а ты своим кодом просто продемонстрировал использование или подобный код действительно для чего-то используется? Если первое, можешь привести какой-нибудь рельный пример, когда нужен Deferred? Если второе, объяснить...

float 30.10.2011 16:42

Просто перед тем как написать диферд, я прикинул что и как должно быть.
Когда видишь что должно получиться легче ориентироваться...

Цитата:

можешь привести какой-нибудь рельный пример
мне такой диферд пригодился для следующего:
в проекте админ панель на попапах(типо как в друпале).
содержимое попапов подгружается не сразу а при клике.
это всё обслуживает 1-н диферд.
там массив стадий с цепочой ajax>animation.
+по статусу стадии удобно смотреть где уже загружен контент а где нет.

а вообще есть 2-я причина такого диферда:
у меня в велосипеде риал-тайм анимация(без очередей как в jquery)
поэтому необходим инструмент согласовки...

x-yuri 31.10.2011 04:12

Цитата:

Сообщение от float
там массив стадий с цепочой ajax>animation.

не знаю, насколько я понял задачу, но почему не сделать как-то так?
new AsyncChain({
    onFailure: function() {
        ...
    }
})
    .add(function(NEXT) {
        new Request({url: ...,
            onSuccess: function() {
                NEXT();
            },
            onFailure: function() {
                this.fireEvent('failure')
            }.bind(this)
        }).post();
    })
    .add(function(NEXT) {
        $(...).get('tween').setOptions({
            onComplete: function() {
                NEXT();
            }
        }).start('left', 100);
    })
    ...
    .run();

float 31.10.2011 05:03

не могу понять из вашего кода что конкретно вы предлагаете поменять...
Цитата:

почему не сделать как-то так?
может и можно.
мой код выглядит примерно также, только без лишних телодвижений.

with-love-from-siberia 31.10.2011 06:43

Тема интересная. Что-то подобное уже обсуждалось в другой ветке на форуме. К сожалению все это по большей части "завязано" на браузерную среду либо "тянет" за собой целый ворох полезных, но не всегда нужных методов. Например, в среде WSH нет методов window.setTimeout/setInterval (да и объекта такого нет, хотя его просто получить, например new ActiveXObject('htmlfile').parentWindow). Тем не менее в WSH встречаются задачи, которые требуют синхронизации данных между основным потоком исполнения и потоком, выполняющимся асинхронно. Конкретный пример - временные потребители, следящие за изменением состояния служб Windows:
var wmi = GetObject('winmgmts:\\\\.\\Root\\CIMV2');
var query = 'SELECT * FROM __InstanceModificationEvent WITHIN 5 WHERE TargetInstance ISA "Win32_Service"';
var sink = WScript.CreateObject('WbemScripting.SWbemSink', 'Sink_');
wmi.ExecNotificationQueryAsync(sink, query);

function Sink_OnObjectReady(e)
{
	if ( e.TargetInstance.State != e.PreviousInstance.State ) {
		WScript.Echo('****');
		WScript.Echo(new Date());
		WScript.Echo(e.TargetInstance.DisplayName);
		WScript.Echo('Current state: ' + e.TargetInstance.State);
		WScript.Echo('Previous state: ' + e.PreviousInstance.State);
	}
};

WScript.Echo('**** Waiting for asynchronous event...');

while ( 1 ) {
    WScript.Sleep(1000);
}


Предлагаемые Deferred и Promise решения выглядят красивыми, но громоздкими. Гораздо проще были бы решения вроде предложенной x-yuri.

x-yuri 31.10.2011 07:35

Цитата:

Сообщение от float
не могу понять из вашего кода что конкретно вы предлагаете поменять...

deferred слишком сложный (слишком много возможностей), я не знаю, где его можно на полную использовать. Я предлагаю максимально упростить функциональность.

Цитата:

Сообщение от float
может и можно.
мой код выглядит примерно также, только без лишних телодвижений.

о каких лишних телодвижениях речь? Давай попробую переписать под твой фреймворк:
new AsyncChain({
    onFailure: function() {
        ...
    }
})
    .add(function(NEXT) {
        _.post('some/url.php').success(function(data) {
            NEXT();
        }).err( this.fireEvent('failure') );
    })
    .add(function(NEXT) {
        _('.menu').hide(200, function() {
            if (this.length) {
                NEXT();
            } else { this.fireEvent('failure'); }
        }.bind(this));
    })
    ...
    .run();

Riim 31.10.2011 10:28

x-yuri, ты приводишь пример для цепочек (как я понял), а как по твоему записывать отслеживание окончания двух и более действий? Т. е. как бы ты записывал вот это:

var dfr1 = new Deferred(function() { console.log('dfr 1'); });
var dfr2 = new Deferred(function() { console.log('dfr 2'); });
 
Deferred.when(dfr1, dfr2).then(function() { console.log('dfr all'); });
 
setTimeout(function() { dfr1.resolve(); }, 2000);
setTimeout(function() { dfr2.resolve(); }, 5000);

x-yuri 31.10.2011 11:21

Цитата:

Сообщение от Riim
а как по твоему записывать отслеживание окончания двух и более действий?

с помощью Deferred, просто передо мной никогда не стояли такие задачи, поэтому и интересуют конкретные варианты применения (может в nodejs...)

Riim 31.10.2011 12:46

Цитата:

Сообщение от x-yuri
поэтому и интересуют конкретные варианты применения

мне понадобилось для отслеживания окончания нескольких анимаций.

x-yuri 31.10.2011 14:43

Цитата:

Сообщение от Riim
мне понадобилось для отслеживания окончания нескольких анимаций.

это не конкретный пример, или другими словами, мне непонятно, что это за анимации, для меня он не конкретный

Riim 31.10.2011 17:03

Цитата:

Сообщение от x-yuri
это не конкретный пример, или другими словами, мне непонятно, что это за анимации, для меня он не конкретный

в моей библиотеке animate может принимать колбэк oncomplete, который срабатывает после завершения анимации всех свойств (при абсолютной скорости они в разное время могут завершаться). Мне не очень нравится, как у меня это там записано, и я не вижу, как это можно записать заметно лучше. Вот и начал в этой теме разбираться, правда пока мне совсем не нравится то, что выходит.

Вот кусок, который меня беспокоит:
// если есть общий oncomplete
if (options && options.oncomplete) {
	// запоминаем его на массиве в котором лежат потоки анимации
	anim.oncomplete = options.oncomplete;
	// options потом попадет в new Animation, записываем в него обработчик, который при каждом вызове пробегает по всем потокам и, если все завершены, вызывает общий обработчик.
	options.oncomplete = function() {
		if (anim.every(function(subanim) { return subanim._progress == 1; })) {
			anim.oncomplete();
		}
	};
}

x-yuri 31.10.2011 17:31

Цитата:

Сообщение от Riim
Мне не очень нравится, как у меня это там записано

а мне нравится :) а почему не нравится?

Riim 31.10.2011 18:16

Цитата:

Сообщение от x-yuri
а почему не нравится?

а хрен его знает, с комментариями вроде читабельно смотрится.

float 11.11.2011 07:01

всётаки решил отказаться от диферд листа.
цацка красивая. но чёт я подумал ещё раз - на основной инструмент не катит.
заюзал более простую концепцию:
сделал _.funcList();
и простейший диферд на основе его.
получается диферд это объект с 2-мя списками функций, который может выполнить только 1-н, 1-н раз.
получается то, что нужно в основном для ajax-а.(в фреймворке по крайней мере)

а на основе фанклиста можно делать другие объекты сколь угодной сложности.
у фанклиста стоит счётчик выполнений, и нет ограничения на число вызовов. это устраняет самую большую проблему прошлой реализации: необходимость заново инициировать объект если что. это не то чтобы недостаток. просто скорее диферд лист - более узкий случай. включать ли его в фреймворк ещё подумаю...

Riim 11.11.2011 15:26

Цитата:

Сообщение от float
сделал _.funcList();

закеж)

x-yuri 11.11.2011 16:20

Цитата:

Сообщение от float
сделал _.funcList();
и простейший диферд на основе его.

насколько я понял это не deferred получается. Основное отличие deferred - возможность отследить завершение нескольких паралелльных процессов.

float 11.11.2011 18:25

Цитата:

насколько я понял это не deferred получается. Основное отличие deferred - возможность отследить завершение нескольких паралелльных процессов.
вроде как это на when больше похоже.

x-yuri 11.11.2011 18:54

я могу ошибаться, я его не использовал ни разу. А в чем еще отличие deferred от просто цепочки функций, которые выполняются последовательно?

float 11.11.2011 19:02

Цитата:

получается диферд это объект с 2-мя списками функций, который может выполнить только 1-н, 1-н раз.
+ в jquery ещё доп методы типо pipe

x-yuri 11.11.2011 20:49

так не в методах же дело. Можно добавить фильтрацию и в цепочку функций. Просто deferred - это параллельное/последовательное выполнение, а цепочка - только последовательное.


Часовой пояс GMT +3, время: 00:24.