Устойчивость системы обработчиков к ошибкам
Классическая система кроссбраузерного добавления событий, описанная в предыдущей статье, неустойчива к ошибкам выполнения обработчиков.
Если какой-нибудь обработчик содержит ошибку, то, генерируя исключение, он ломает цикл вызова остальных обработчиков текущего элемента/события.
Получается, что обработчики зависят друг от друга. Есть различные способы, как этого избежать.
Можно завернуть вызов каждого обработчика в 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
}
}
- Инициализуется массив
errors для исключений
- Все исключения - добавляем в массив, при этом не прерывая цепочку обработчиков.
- Если была только одна ошибка - кидаем ее дальше. Иначе делаем общий объект
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).
Их можно с успехом использовать на практике.
|
Я оборачиваю вызов каждого обработчика в try, и в catch делаю setTimeout с повторной генерацией оригинальной ошибки. В результате, и выполнение обработчиков не прерывается, и ошибка будет показана родная, то есть будет показана и строка в которой возникла ошибка и прочая инфа.
Спасибо вам за статьи. Сам бы только много позже обратил бы внимание на такой момент
правильная строка будет показана лишь в фф, а остальные покажут куда попало.
а вообще, забавно - сначала отказываемся от нативных событий эмулируя их через свой объект, а потом для вызова каждого обработчика генерируем нативное событие, чтобы он не ронял остальные хD не проще ли сразу честно вешать обработчики на события?
.ня
не проще ли сразу честно вешать обработчики на события?
Если бы евенты во всех браузерах были одинаковые, тогда и не было б необходимости отказываться от нативных событий эмулируя их через свой объект
Не вижу проблемы в том, что при падении одного обработчика, прекращается выполнение других.
Если в каком-то обработчике ошибка - значит приложение в целом неработоспособно, и выполнять остальные обработчики нет смысла.
Не было бы причин отказываться от the backrooms нативных событий в пользу эмуляции объектов, если бы они были согласованы во всех браузерах.