Упрощенный 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. |
а слабо разобраться в этой штуке? ;-) http://forum.mozilla-russia.org/viewtopic.php?id=51812
|
Цитата:
|
угу, только с поддержкой исключений и на яваскрипте
|
для своего велосипеда сделал диферд следующей структуры:
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 есть). и тд и тп. но решил оставить. уже пару раз, с удивлением, словил себя на мысли, когда юзал его, мол "вау. так пригодилось, а я о таком даже не подумал". но чёт до сих пор сомневаюсь... |
я правильно понял, что на каждую созданную стадию, вы можете навешивать свои собственные обработчики, и вызывать их обработку для конкретной стадии?
По-сути, можно заменить массивом независимых 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');
});
Вот. Я было попробовал это переписать короче - не получилось... Ещё из плюсов: не засирается область лишними переменными. +я изначально диферд сделал простым. тоесть все свойства создаются в момент навешивания обработчиков. так что можно не париться что колбасу режем скальпелем. |
судя по последним строчкам вы сделали DeferredList, который позволяет обращаться через себя к отдельным Deferred
Плюс добавление наблюдаемых объектов на лету. Вопрос на засыпку: что будет, если я после d.success() добавлю, а позже выполню еще одну стадию? сработает ли еще раз d.success с дополниельной стадией? |
Цитата:
то есть после выполнения диферда на него можно навешать ещё стадий. и выполнять их. но обраьботчики на дифферде сработают только 1 раз. признаться про это я не думал, когда делал(ну или мб забыл, ночь всё таки была:)). Цитата:
я забыл упомянуть, что и у самого диферда есть d.finish(). тоесть его самого можно юзать как простейший $диферд. d.anyway(function() {...}).anyway(function() {...}).anyway(function() {...}); d.finish(); В общем АД:D |
Цитата:
Цитата:
resolve-ит все объявленные стадии и глобальный обработчик? |
Цитата:
и тут есть запарка, когда его вызывают из вне. при таком вызове точно не ясно что делать с обработчиками success и err... сейчас если диферд без стадий вызывается success(тк reject-а не было) и anyway |
Цитата:
Ведь, тогда вы обманываете обработчики, говоря им, что событие уже выполнилось, но результатов события получить при этом нельзя |
Цитата:
но есть другой момент. у диферда вообще может не быть стадий. а success err anyway можно вешать. логично иметь метод чтобы их вызвать... |
только я думаю надо чуть изменить.
finish зарыть. а success err anyway можно сделать разное поведение в зависимости от аргументов. функция - добавление в очередь. иначе вызов обработчиков в контексте с параметрами. |
float, а ты своим кодом просто продемонстрировал использование или подобный код действительно для чего-то используется? Если первое, можешь привести какой-нибудь рельный пример, когда нужен Deferred? Если второе, объяснить...
|
Просто перед тем как написать диферд, я прикинул что и как должно быть.
Когда видишь что должно получиться легче ориентироваться... Цитата:
в проекте админ панель на попапах(типо как в друпале). содержимое попапов подгружается не сразу а при клике. это всё обслуживает 1-н диферд. там массив стадий с цепочой ajax>animation. +по статусу стадии удобно смотреть где уже загружен контент а где нет. а вообще есть 2-я причина такого диферда: у меня в велосипеде риал-тайм анимация(без очередей как в jquery) поэтому необходим инструмент согласовки... |
Цитата:
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();
|
не могу понять из вашего кода что конкретно вы предлагаете поменять...
Цитата:
мой код выглядит примерно также, только без лишних телодвижений. |
Тема интересная. Что-то подобное уже обсуждалось в другой ветке на форуме. К сожалению все это по большей части "завязано" на браузерную среду либо "тянет" за собой целый ворох полезных, но не всегда нужных методов. Например, в среде 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. |
Цитата:
Цитата:
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();
|
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);
|
Цитата:
|
Цитата:
|
Цитата:
|
Цитата:
Вот кусок, который меня беспокоит:
// если есть общий 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();
}
};
}
|
Цитата:
|
Цитата:
|
всётаки решил отказаться от диферд листа.
цацка красивая. но чёт я подумал ещё раз - на основной инструмент не катит. заюзал более простую концепцию: сделал _.funcList(); и простейший диферд на основе его. получается диферд это объект с 2-мя списками функций, который может выполнить только 1-н, 1-н раз. получается то, что нужно в основном для ajax-а.(в фреймворке по крайней мере) а на основе фанклиста можно делать другие объекты сколь угодной сложности. у фанклиста стоит счётчик выполнений, и нет ограничения на число вызовов. это устраняет самую большую проблему прошлой реализации: необходимость заново инициировать объект если что. это не то чтобы недостаток. просто скорее диферд лист - более узкий случай. включать ли его в фреймворк ещё подумаю... |
Цитата:
|
Цитата:
|
Цитата:
|
я могу ошибаться, я его не использовал ни разу. А в чем еще отличие deferred от просто цепочки функций, которые выполняются последовательно?
|
Цитата:
|
так не в методах же дело. Можно добавить фильтрацию и в цепочку функций. Просто deferred - это параллельное/последовательное выполнение, а цепочка - только последовательное.
|
| Часовой пояс GMT +3, время: 13:22. |