42 строки кода для выхода из лимба

в 9:07, , рубрики: emmiter, event loop, Events, javascript, node.js, Разработка веб-сайтов

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

Но в один прекрасный день… Правильно! Прилетает мыло «ваша программа не работает» ((С) 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 строки кода, мы решили весьма редкую, но довольно пакостную проблему с зацикливанием событий. Кто знает (я вот не знаю), может и вам оно пригодится. Все тут.

Счастья, добра и электричества в ваши дома.

Автор: AlexWriter

Источник

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


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