| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > JavaScript: Общие вопросы > Цепочка функций |
| Автор: Dr.Zlo 28.1.2008, 11:07 |
| В больших скриптах сталкиваюсь с необходимостью разделения анимации и логики, т.е. к примеру сначала элемент нужно красиво показать\передвинуть\подсветить, потом загрузить в него инфу, после загрузки инфы задвинуть\переместить\раздвинуть элемент, и самое главное все эти действия нужно выполнять ПОСЛЕДОВАТЕЛЬНО, следующий этап начинается только после завершения предыдущего. В основном работаю с jQuery, но если кто знает решение с помощью стандартных средств js, пишите, буду только рад. Мне нужно решение из серии: chain(animate1(), logic1(),animate2()...) или animate1().logic1().animate2()... Сразу скажу, стандартное решение jQuery для callback функций анимации меня не устраивает, т.е. $('#elem').animate({.....}, speed, 'linear', function(){ logic1() }) - это мне не подходит |
| Автор: ksnk 28.1.2008, 12:01 |
| А чем не устраивает стандарное решение, можно поподробнее? |
| Автор: Dr.Zlo 28.1.2008, 13:53 | ||||
Стандартное решение не утсраивает тем, что в таком случае в скрипте получается каша из визуальных эффектов и логических функций, к тому же если нам надо в каждую функцию передавать различные параметры, будет вообще ужас, если к примеру после анимации anim(x) надо выполнить logic(args), затем anim2(y), затем logic2(args2) нужно исполнять что-то вроде:
Другой пример: нам надо выполнять одну и ту же функцию anim() перед выполнением logic(args) и logic2(args2), тогда вообще получается жёсткая жесть:
И если не дай бог в args или args2 передаётся объект, тогда вообще придётся создавать глобальные переменные, а это вообще не фэн-шуй. Плюс ко всему было бы удобно и красиво, если бы вся анимация была в одном js файле, а вся логика в другом, а связывались бы они с помощью тех самых цепочечных вызовов функций, в таком случае было бы очень удобно менять эффекты не копаясь в логике или модифицировать логику, не разгребая эффекты. |
| Автор: ksnk 28.1.2008, 16:12 | ||
| Дык взять и написать объект chain, который представляет собой массив функций, каждая из которых вместо callback'а вызывает chain.next() chain.next обязан выкинуть из собственного массива первую функцию и вызвать ее.
пойдет? |
| Автор: Dr.Zlo 28.1.2008, 16:28 |
| - во-первых, такой способ принципиально не отличается от банального вызова следующей функции в конце предыдущей - во-вторых опять же возникает проблема передачи переменных - в-третьих здесь функции жёстко привязываются к цепи, а ведь идея состоит в том, что функции могут вызываться как цепочечно, так и по отдельности. Нужна некая chain конструкция, для работы которой не пришлось бы залезать в функцию и прописывать ей callback вжёсткую. В данном случае основную проблему я вижу в том, чтоб определять момент, когда текущая функция цепочки закончила работу, чтоб передать управление следующей функции, просто мне мозга пока не хватает придумать, как это реализовать |
| Автор: ksnk 28.1.2008, 16:58 |
| Dr.Zlo, Я не очень пока понимаю суть возражений. Напишите как бы вы хотели писать |
| Автор: Dr.Zlo 28.1.2008, 17:18 | ||
ksnk, в идеале хотелось бы так:
Но к сожалению уже на уровне передачи вызовов функций в качестве параметров для chain эти функции пытаются выполниться, так что можно что-то вроде : chain([{ 'start' : ['Hello!']}, {'process' : ['processing']}, {'end' : []}); |
| Автор: ksnk 28.1.2008, 17:47 | ||
Ну, более реально записать вот так
правда с таймаутом будет затык. Только сам таймаут знает когда он закончится... |
| Автор: Dr.Zlo 28.1.2008, 18:13 | ||
В том-то и дело, у меня часто получаются конструкции из серии:
Так что если никто не знает как на завершение функции повесить событие, придётся мне смириться с тем, что в каждой функции нужно будет прописывать callback, что-нибудь вроде chain.next(); Кстати, тут ещё одна проблема. Если есть функция например с тремя параметрами start(a,b,c) и мы пишем: chain(start : ['first', 'second', 'third']).chain(...).chain(...) Как потом в теле chain вызвать start и передать ему эти 3 параметра? и что будет если один параметр- строка, второй- объект, 3й например число? |
| Автор: ksnk 28.1.2008, 18:17 | ||
| Решением было бы зафиксировать интерфейс "функций анимации". К примеру - они принимают 2 параметра. Первый - параметри или объект с реальными параметрами функции, второй - callback, который должен вызваться по завершении функции. если CallBack'а нет - не вызываем ничего. тогда примерно вот что получается
|
| Автор: ksnk 29.1.2008, 01:22 | ||
| В http://forum.vingrad.ru/forum/topic-193081/15.html я уже даже сие применил... правда не особо показательно... Окончательный интерфейсные соглашения
chain() - вызов без параметров инициализирует новый объект chain chain(a,b) - поместить в очередь вызов функции a с праметрами b и chain.next chain.run() - запуск очереди на выполнение. Можно вызывать неоднократно. Уже запущеннй процесс так не остановить. chain.clear() - остановить цепочку Каждая внешняя функция возвращает себя как результат и по этому можно писать цепочки таких вызовов... Листинг функции chain
|
| Автор: Dr.Zlo 29.1.2008, 10:52 | ||
| ksnk, спасибо! Поюзал твою функцию, решил немного модифицировать, хочу чтоб выполняемые функции не пришлось сильно кромсать и передавать объект вместо набора переменных. Вот что получилось:
Вызов будет таким: chain(start_func , ['Превед ', ' всем ', ' патцанам!']).chain(alert_func , ['stop!!!']).run() Возникает 2 проблемы: 1. После выполнения последнего элемента chain, FireBug выдаёт ошибку: "x has no properties" 2. this.timeout=x.a(x.b[0], x.b[1], x.b[2], x.b[3], x.b[4], x.b[5], x.b[6],x.b[7],x.b[8],x.b[9]) - это позор программиста, но если никто не знает как это обойти и при этом передавать в функцию все переменные в нормальном виде, а не объектом, то я лучше оставлю так |
| Автор: ksnk 29.1.2008, 15:20 | ||||
| 1. У меня чего-то не выдает... :( 2. написать вот так
Я, в принципе думал, что нужно бы проверять тип второго параметра и если это массив - вызвать apply, но меня остановило то, что в жизни бывают и "просто массивы" Добавлено @ 15:26 для пущего единообразия можно писать вот так.
|
| Автор: ksnk 29.1.2008, 15:57 |
| 1. У меня тоже выдает.... :-( Посмотрим - поковыряем... |
| Автор: ksnk 29.1.2008, 17:24 | ||
Вот вариант, который получился в итоге. Правила оформления функций анимации несколько поменялись...
P.S. поправлена ошибка в функции chain.wait... P.P.S почищен код, благодаря AKS P.p.P.S поправлен метод clear, из-за чего были глюки с исполнением "аварийного" chain'а |
| Автор: Dr.Zlo 29.1.2008, 18:12 | ||
Я избавился от ошибки следующим образом:
После этого нормально стало всё работать и не косячить после последнего элемента. Если честно я до сих пор до конца не втыкаю как работает твой chain, это решение я нашёл интуитивно. Но по ходу ты пофиксил тем же способом и ужё ушёл намного дальше в разработку chain Вечером посмотрю и протестю твой новый вариант. |
| Автор: AKS 29.1.2008, 20:25 |
Смотрю и не могу понять - когда условие this instanceof Array будет истинным? Когда Вы "провернете" http://forum.vingrad.ru/forum/topic-73457/anchor-entry1396256/15.html#? |
| Автор: ksnk 29.1.2008, 21:35 |
| AKS, Нееееее! Тут как раз все значительно интереснее ;-) объект chain это и ЕСТЬ настоящий массив честно сделанный через new Array! Добавлено через 42 секунды this instanceof Array - проверка на то, вызывают ли нас первый раз. Тогда this указывает на объект window |
| Автор: ksnk 29.1.2008, 23:28 |
| AKS, Впрочем, Вы как всегда правы! Первоначальное решение было опубликовано выше несколькими постами и последующие варианты лишь "облагораживали" его. Проявившееся желание написать chain(x).chain(y).chain(z)... породило совершенно логичную мысль о том, что каждая функция chain выдает некий объект как результат. причем первый chain в цепочке должен его (этот объект) породить. А так как по сути первый и последующие элементы цепочки одинаковы по входным параметрам, то родилась уникальная мысль написать конструкцию function chain(){..var x = new chain().... this.chain=chain/* Yoooo!!!*/ ...}; chain.prototype=new Array();, все в одном флаконе. ;-) Когда пришла пора "со всей этой фигней взлетать", возникла задача по определению контекста вызова chain(), new chain(), и .chain'а, рудиментарное наследие от которой до сих пор сидела в коде. по дороге напоролся на "баг", не позволяющий наследоваться от Array запланированным способом. Хорошо, что внезапно посетивший меня приступ здравого смысла заставил несколько оптимизировать код, но, к сожалению, не до конца. Так что, AKS, спасибо! |
| Автор: Dr.Zlo 30.1.2008, 09:32 | ||
| ksnk, вчера внедрил chain в довольно большое приложение. Всё отлично работает, Но есть несколько пожеланий. 1. Иногда нужно собрать chain по кусочкам, и только потом выполнить, или к примеру иногда удобней объявить chain где-нибудь в начале скрипта, а вызывать в нужных местах. Допустим у нас есть задача: для всех чётных элементов списка выполнить функцию по цепочке. Вот как я это вижу:
Было бы прикольно, если бы такая конструкция работала 2. После завершения chain лучше избавляться от созданного массива (обnullять его, что-ли), ведь если все эти массивы будут висеть в памяти, после нескольких вызовов chain скрипт будет заметно тормозиться |
| Автор: ksnk 30.1.2008, 10:00 |
| Честно говоря меня преследует вопрос эстетический ;-) Удобно ли то, что "первая" функция называется Chain (с большой буквы). Можно было бы ради единообразия назвать ее chain (с маленькой), как и "собственный" метод. На каком-то этапе мне показалось идеологически более верно то, что первый элемент цепочки пишется с большой буквы. Правда до серьезного практического применения этой "идеологии" я пока не добрался. |
| Автор: Dr.Zlo 30.1.2008, 10:41 |
| По поводу именования. Думаю по фэн-шую было бы правильно создавать цепь, потом присоединять к ней звенья. Цепь по-английски chain, по поводу звеньев яндекс словари говорят следующее: звено ср. 1) link 2) (механизма или структуры) unit, section; part, branch 3) team, group, unit 4) (самолетов) flight 5) воен. level, echelon, squad Так как с link будут несколько другие ассоциации, то можно взять unit для определения звена цепи. В итоге можно договориться использовать конструкцию вроде: chain().unit(func1, param1).unit(func2, param2).unit(func3, param3).run(); В крайнем случае оставить всё как есть, только избавиться от Chain с большой буквы, он путает и сбивает с толку, как мне кажется |
| Автор: ksnk 30.1.2008, 11:03 |
| ok! Надо будет при случае поменять большую букву... Насчет unit'а - я против... ;-) ; - я предполагал первонально, что будет вот так chain(func1, param1).chain(func2, param2).chain(func3, param3).run(); Единая сущьность для первой и последующих итераций... Imho, пусть так и остается. Всех изменений в скрипте, если надо - переназвать "головную" функцию... |
| Автор: Sardar 30.1.2008, 14:53 | ||
| Уже вторая страница, а я что-то не пойму сути вопроса... Оно?
|
| Автор: Dr.Zlo 30.1.2008, 14:58 | ||
Всё-таки отсроченный запуск chain не работает, я тестировал, просто не запускается и всё. Вот исходник:
Кстати, ещё одна интересная тема для размышлений- можно ли chain замкнуть на саму себя, чтоб все операции выполнялись в цикле заданное количество раз, либо бесконечно, пока мы насильно не остановим chain? Добавлено @ 15:08 Sardar, в твоём случае функции выполняются одновременно, а суть chain состоит в том, чтоб выполнять их последовательно, чтоб к примеру сначала выполнилась анимация, после завершения анимации выполнилась логика, после завершения логики вторая анимация и т.п. В этом-то и суть вопроса |
| Автор: ksnk 30.1.2008, 15:35 | ||||
Согласен... Зато твой - определение "нечетности", а не "четности" ;-) Вот такая правка исправит ситуацию... Первым элементом цепочки оказывалась undefined конструкция, которая генерировалась Chain()'ом...
|
| Автор: ksnk 30.1.2008, 16:13 | ||
Да , еще вот это я поменял
Добавлено через 57 секунд Хотя, как было - тоже работает ... |
| Автор: ksnk 30.1.2008, 17:28 | ||
Обновил http://forum.vingrad.ru/index.php?showtopic=193391&view=findpost&p=1396039... теперь в этом примере она еще и прекращать анимацию, в том числе и от jQuery, умеет корректно...
|
| Автор: Sardar 30.1.2008, 18:46 | ||
А ты попробуй |
| Автор: ksnk 30.1.2008, 19:26 | ||
| Sardar, имеется ввиду, что в цепочке могут присутствовать функции, которые не заканчиваются фактом своего вызова. К примеру, выставляющие мрачный таймаут и чекающие какое-нибудь странное состояние. Про такие функции только она сама может сказать, когда-же она пожелает закончится ;-) Пример - получение Ajax'ом странички. Я сейчас активно думаю над возможностью писать chain(get_ajax,'mypage.php?action=hello') .chain(pace_into_div,[null,null,'div_id']) // null - placeholder для RequestText, RequestError Моя схема позволяет вызываемой функции самой определить время своего завершения и вызвать следующий элемент в цепочке... А Ваша? Добавлено через 9 минут и 1 секунду
Хм... вставлять в chain операторы ветвления по условию, циклы и свитчи... + парраллельное исполнение анимаци... В принципе, можно как-то обойтись и существующей схемой. Достаточно иметь указатель на головной chain в исполняемой процедуре. Сама процедура уже может навставлять в массив всего чего пожелает... Надо только разумно ограничить ее желания ;-) В принципе, указатель на головной chain можно получить, вызвав arguments.calee.next с каким- нибудь характерным параметром... В общем - нужно подумать... |
| Автор: Dr.Zlo 30.1.2008, 23:56 | ||||
Кстати, вот примитивная реализация цикличности. Тупо и забавно
Добавлено через 3 минуты и 45 секунд Кстати,
не забываем, что в массивах чётные элементы имеют нечётные индексы ;-) |
| Автор: ksnk 9.2.2008, 01:40 |
| В функции обнаружился баг. Связан он с хранением "цепочки" в самой функции анимации. Если используется анимирование сразу в несколько потоков, цепочки загадочным образом путаются с труднопредсказуемым результатом... Было принято решение откатится до фиксированного формата функций анимации. Правда здравый смысл все-таки немного победил, теперь они получают 3 параметра, 2 из которых - нормальные параметры, а 3-й callback функция для вызова. Во вложении сам chain и примерчик его использования. |