Javascript.RU

Устойчивость системы обработчиков к ошибкам

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

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

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

Можно завернуть вызов каждого обработчика в try .. catch. Тогда исключение будет поймано.

Что делать дальше?

Есть варианты.. Например, библиотека Yahoo UI хранит последнее пойманное исключение в специальной переменной lastError. А фреймворк Mochikit собирает все пойманные исключения в errors.

В коде ниже проиллюстрирован подход из Mochikit:

function commonHandle(event) {
  event = fixEvent(event)
  handlers = this.events[event.type]
  
  // (1)
  var errors = []
 
  for ( var g in handlers ) {
    try {
      var ret =  handlers[g].call(this, event)
      if ( ret === false ) {
        event.preventDefault()
        event.stopPropagation()
      }
    } catch(e) {
      // (2)
      errors.push(e)
    }
  }
  
  // (3)
  if (errors.length == 1) {
      throw errors[0]
  } else if (errors.length > 1) {
      var e = new Error("Multiple errors thrown in handling 'sig', see errors property");
      e.errors = errors
      throw e
  }  
}
  1. Инициализуется массив errors для исключений
  2. Все исключения - добавляем в массив, при этом не прерывая цепочку обработчиков.
  3. Если была только одна ошибка - кидаем ее дальше. Иначе делаем общий объект Error со списком ошибок в свойстве errors и кидаем его.

Каждый вызов обработчика можно завернуть в setTimeout.
Пожалуй, это самый простой способ.

// вместо
handlers[g].call(this, event)
// поставить
setTimeout(function() { handlers[g].call(..) }, 0)

При этом - да, обработчики будут вызваны независимо. Но к моменту, когда сработает setTimeout - событие будет уже "мертво", нельзя будет ни вызвать preventDefault, ни отменить всплытие.

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

Поэтому, увы, этот способ никак не годится.

Этот подход описан Dean Edwards в статье Callbacks and Events

Он заключается в том, что каждое событие заворачивается не в setTimeout, а в dispatchEvent (fireEvent для IE).

Таким образом, события вызываются в независимых потоках выполнения и инициируют ошибки независимо.

Есть несколько вариантов, как сделать систему событий устойчивой к ошибкам.
Жизнеспособны - первый подход (YUI/Mochikit) и последний (Dean).

Их можно с успехом использовать на практике.


Автор: Гость (не зарегистрирован), дата: 14 апреля, 2011 - 07:28
#permalink

Я оборачиваю вызов каждого обработчика в try, и в catch делаю setTimeout с повторной генерацией оригинальной ошибки. В результате, и выполнение обработчиков не прерывается, и ошибка будет показана родная, то есть будет показана и строка в которой возникла ошибка и прочая инфа.

// Фрагмент общего обработчика для MISE
// только для msie
        // this -- нод на котором происходит событие
        handlers = this[eventsPropertyName][event.type]
        // ... пропущена инициализация fixEvent
        // копирование недостающих свойств в event msie
        for (var propertyName in fixEvent) {
            if (!event[propertyName]) {
                event[propertyName] = fixEvent[propertyName]
            };
        };

        // для всех обработчиков повешенных на текущий нод для текущего типа события
        for (var i in handlers) {
            try {
                ret = handlers[i].call(this, event);
            } catch (err) {
                // собственно вот:
                setTimeout(
                    (function (err) {
                        return function () { throw err }
                    })(err), 0
                );
            };
            if (ret === false) {
               returnValue = false;
            };
        };
        return returnValue;

Спасибо вам за статьи. Сам бы только много позже обратил бы внимание на такой момент


Автор: tenshi, дата: 14 июля, 2011 - 20:02
#permalink

правильная строка будет показана лишь в фф, а остальные покажут куда попало.

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

.ня


Автор: Гость (не зарегистрирован), дата: 15 декабря, 2011 - 15:12
#permalink

не проще ли сразу честно вешать обработчики на события?

Если бы евенты во всех браузерах были одинаковые, тогда и не было б необходимости отказываться от нативных событий эмулируя их через свой объект


Автор: zenitchik (не зарегистрирован), дата: 13 августа, 2012 - 12:16
#permalink

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


Автор: Tamera Schaefer (не зарегистрирован), дата: 3 августа, 2023 - 13:18
#permalink

Не было бы причин отказываться от the backrooms нативных событий в пользу эмуляции объектов, если бы они были согласованы во всех браузерах.


 
Текущий раздел
Поиск по сайту
Содержание

Учебник javascript

Основные элементы языка

Сундучок с инструментами

Интерфейсы

Все об AJAX

Оптимизация

Разное

Дерево всех статей

Последние темы на форуме
Forum