42 рядка коду для виходу з лімба

Ви ж знаєте, як це буває: великий проект довго проектується, довго пишеться, часом вимучується і зрештою здається. Проходить місяць іншої «гарячої налагодження», і після настає побожна тиша. Від замовника нічого не чути. І не тому що він розорився завдяки вашим зусиллям; рахунки за телефон у нього не оплачені, а інтернет давно відключений, немає) Просто у нього все працює в штатному режимі.

Але в один прекрасний день… Правильно! Прилітає мило «ваша програма не працює» (© bash), телефони розігріваються до красна, а юристи нервово перечитують, що вони там накидали в розділ «гарантійне обслуговування».

Рівно така ситуація була і у нас. Робили ми доволі об'ємний проект, суть якого можна було б описати так (коротко, звичайно): є різний контент (клієнтська база, маркетингова база, база зв'язків і інше, інше, інше) і різні способи його подання (widget, popup, modal etc.). Іншими словами, з нашого боку була підготовлена платформа (API доступу до даних, візуалізація, вся, як це модно говорити, екосистема (хоча я не знаю, що це означає, але звучить дуже круто)), щоб розробники замовника могли писати свої контролери даних і просто файликом їх «класти» у вказане місце, після чого щасливо споглядати, як з'являється новенький віджет зі списком поточних котирувань по якому-небудь мудреному індексу.

І як я вже сказав, все складалося добре. Провели кілька «майстер» класів, все показали, все розповіли, випили пива і закрутилося. Вже без нас.

Поки все не зламалося. Саме так: «все» і «зламалося». У якісь моменти додаток просто стало намертво виснути. Та так, що вкладку браузера не закриєш. Мало-мальськи досвідчений web-developer тут же скаже – у вас цикл десь заклинило дітлахи. І буде правий, що вже там.

Але перш ніж перейти до тим самим 42 рядків коду, я лише нагадаю, що коли є додаток з купою всяких смаколиків всередині, то кращий спосіб організації комунікації між ними – це внутрішні події. І у нашого в момент складання проекту їх було під сотню, а коли почалися проблеми їх число збільшилося ще на пару десятків.

І, як ви вже здогадалися, «клинило» як раз контролер подій. На пальцях: подія A, викликає подія B, а подія B – подія C, а воно, у свою чергу, знову викликає подія A. Та-да-м, зустрічайте цикл!

Наш обробник подій був до неподобства простий і тулився в файлику на 44 рядках коду. Однак він не вмів робити дуже актуальну річ – перевіряти не в циклі він.

Багато пити думати не довелося і рішення знайшли досить швидко. Хороше воно чи погане – це все на ваш суд. Опишу лише основну ідею.

Єдиний спосіб перевірити «хто» викликав ланцюжок подій (у нашому прикладі знайти A, B і C) – це перевірити stack. Щоб отримати stack потрібно просто «викинути» помилку.

Залишається проблема – як «позначити» місце виклику події, адже в стеку повинно бути щось, що допомогло б розпізнати весь ланцюжок раніше запущених подій? Одне з рішень цієї проблеми – зазначені функції-обгортки, в імені яких як раз і зберігається вся інформація про попередні події. Нічого не зрозуміли? Я ось написав, перечитав і теж не зрозумів. Простіше подивитися код.

Загалом тепер, якщо виконати це (подія A викликає B, B викликає, а C знову викликає A):

var safeevents = new SafeEvents();
safeevents.bind('A', function () {
safeevents.trigger('B');
});
safeevents.bind('B', function () {
safeevents.trigger('C');
});
safeevents.bind('C', function () {
safeevents.trigger('A');
});
safeevents.trigger('A');


На цей раз програма не піде в лімб, а викине в консоль виняток «Uncaught Error: Event [A] called itself. Full chain: A, B, C». Profit. Тепер розробнику не потрібно йти на три додаткових перекуру, щоб збагнути в чому власне справа – все видно з повідомлення в консолі.

З асинхронними викликами трохи складніше. Для них, на жаль, потрібно виконувати додаткове дію. Але воно настільки крихітні, що навряд чи буде великою проблемою.

var safeevents = new SafeEvents();
safeevents.bind('A', function () {
safeevents.trigger('B');
});
safeevents.bind('B', function () {
safeevents.trigger('C');
});
safeevents.bind('C', function () {
/*
* Use method "safely" to wrap your async methods and create safe callback.
*/
setTimeout(safeevents.safely(function () {
safeevents.trigger('A');
}), 10);
});
safeevents.trigger('A');


Зверніть увагу на функцію зворотного виклику в таймері. Ми додаємо «обгортку», щоб передати дані про попередні події в асинхронних виклики. І знову в консолі ми побачимо: «Uncaught Error: Event [A] called itself. Full chain: A, B, C».

Звичайно, не завжди треба брати так і нахабно викидати виняток. Значно краще тихенько повідомити куди слід записати дані в лог або відправити повідомлення адміну. Для чого можна поставити свій обробник на випадок «зациклення» і отримати всі необхідні дані.

var safeevents = new SafeEvents();
safeevents.bind('A', function () {
safeevents.trigger('B');
});
safeevents.bind('B', function () {
safeevents.trigger('C');
});
safeevents.bind('C', function () {
safeevents.trigger('A');
});
safeevents.bind(safeevents.onloop, function (e, chain, last_event, stack) {
console.log('Error message:' + e);
console.log('Full chain of events:' + chain.join(', '));
console.log('Last event (generated loop):' + last_event);
console.log('Error stack:' + stack);
});
safeevents.trigger('A');


Тепер наше додаток зовсім не переривається, але цикл при цьому буде успішно запобігли.

Загалом переписавши наш найпростіший обробник подій і отримавши додатково 42 рядка коду, ми вирішили дуже рідкісну, але досить капосну проблему з зацикленням подій. Хто знає (я от не знаю), може і вам знадобиться. тут.

Щастя, добра і електрики у ваші будинки.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.