11: Обработка ошибок в JavaScript
Вы написали приложение JavaScript, и оно работает отлично - пока вы не получите сообщение об ошибке. Это нечто неприглядное, выскакивающее на экране, что-нибудь вроде 'myObject.fields is null or not an object'. Что это значит? В этом уроке мы рассмотрим, как избавиться от ошибок, и покажем несколько различных методов для общей обработки ошибок.
Синтаксические ошибки
JavaScript имеет два основных уровня обработки ошибок: синтаксические ошибки и ошибки времени выполнения. Синтаксические ошибки возникают до выполнения кода JavaScript, означая в основном, что код невозможно компилировать. Возьмем, например, следующий код:
for(var i=0; i<10; i++) // рабочий код здесь }
Обратите внимание, что здесь пропущена открывающая скобка {. Если попробовать выполнить этот код, то немедленно появится сообщение об ошибке, даже если код находится в функции, которая не выполняется сразу. Такие ошибки почти всегда легко находятся и исправляются. В этом случае будет получено сообщение, говорящее что-нибудь подобное " Ожидалась ')', строка 10, позиция 18". Если перейти к строке 10, то там обычно будет находиться достаточно очевидная ошибка, такая, как пропущенная ), дополнительный знак <или какая-то другая опечатка. С такими ошибками ничего нельзя сделать, кроме как просто исправить и двигаться дальше. Ниже представлен список некоторых наиболее распространенных синтаксических ошибок:
- Пропущенные или непарные фигурные, круглые или квадратные скобки
Каждая фигурная {, круглая (, или квадратная [ скобка должна иметь свою закрывающую парную скобку. Если имеются вложенные скобки, то внутренние должны быть закрыты прежде, чем внешние. Например, набор скобок {[}] является недопустимым.
Условия операторов if, for и while должны помещаться в круглые скобки. Выражнение "if x=5{" является недопустимым, так как "x=5" должно быть заключено в круглые скобки. Если с этим возникнут проблемы, то существуют редакторы, такие, как EditPlus, которые могут выделять соответствующие пары скобок, и т.д.
- Пропущенные или непарные кавычки
Это очень распространенная проблема. Строки в JavaScript начинаются символом 'или " и должны заканчиваться таким же символом. Если этот символ существует в строке, то он должен быть экранирован. Например, код
var x = 'It's a beautiful day';
является недопустимым, потому что ' в It's не экранировано. Этот код должен выглядеть следующим образом:
var x = 'It\'s a beautiful day'; // или var x = "It's a beautiful day";
Еще одной достаточно распространенной ошибкой является завершение строки другим символом, т.е.:
var x = "It's a beautiful day';
Эта строка начинается с символа ", поэтому должна закончиться также символом ".
- Пропущенная точка с запятой
Хотя точки с запятой обычно не нужны в JavaScript, но лучше все же их использовать. Например, если нужно сократить файл JavaScript, то обычно удаляют все переносы строк. Возьмем следующий код:
var x=5 var y=10
Если удалить переносы строк, то получим код
var x=5 var y=10
который вызовет ошибку. Если бы использовались точки с запятой, то проблемы не было бы.
Ошибки времени выполнения
Перейдем к ошибкам времени выполнения. После запуска кода на исполнение начинают появляться ошибки времени выполнения. Эти ошибки могут возникать в связи с множеством причин. Каждый из следующих далее блоков кода будет порождать ошибку:
alert(x); // 'x' не определено var x; x[5] = 'test'; // 'x' будет null или не является объектом window.frames = 5; // Не реализовано var for; // ожидается идентификатор document.doesNotExist(5); // объект не поддерживает это свойство или метод alert(parseInt('5')); // ожидается объект
Многие из этих проблем вызываются более общими ошибками, которые приходится разыскивать.
- Неправильное использование прописных букв
Все встроенные функции JavaScript используют специальную форму записи имен функций, предполагающую, что имя функции начинается со строчной буквы, а в начале каждого следующего слова будет использоваться прописная буква: parseInt, getElementById, createElement, appendChild, и т.д.
Так как JavaScript учитывает регистр символов, то неправильный ввод имени одной из этих функций часто будет приводить к ошибке во время выполнения.
- Ссылка на несуществующий код, функции или объекты DOM
Эта проблема возникает обычно в отношении объектов DOM. Предположим, что имеется код, который изменяет некоторые элементы формы на странице. Если делается попытка выполнить этот код до появления элементов формы, например, если поместить его в тег <HEAD>, то будет получена ошибка JavaScript.
Обычно эта проблема легко решается. Лучшим решением будет выполнение кода по событию onload, например:
<BODY onload="loadFunction();">
или еще лучше, присоединение события к загрузке тела.
- Использование зарезервированного слова
Существует длинный список зарезервированных ключевых слов JavaScript. Если делается попытка использовать многие из них вне их специального контекста, как, например, запись
var for = 5;
то будет возникать ошибка.
- Использование пропущенного параметра
При определении функции обычно используется некоторое количество аргументов. Если некоторые из этих аргументов пропущены и делается попытка их использовать, то возникнут ошибки.
Большинство из этих проблем попадают в категорию опечаток и просто обычных ошибок, которые можно исправить, но необходимо о них знать, чтобы случайно не сделать.
Однако последний тип ошибки из этого списка с пропущенными параметрами можно проверить достаточно легко:
function myFunction(a, b, c){ if(a){ // выполняется работа с a } if(b && c){ // выполняется работа с b и c } }
Если функция вызывается только с одной переменной, то проблемы не возникает. Однако надо помнить об одной вещи: если входящая по значению переменная может быть определена как false (0 или false), то код не будет работать. В связи с этим лучше проверять, что переменная не была определена:
function myFunction(a, b, c){ if(typeof(a)!='undefined'){ // выполнение кода с a } if((typeof(b)!='undefined') && (typeof(c)!='undefined')){ // выполнение кода с b и c } }
В этом случае, даже если одна из переменных будет передана как 0, false или null, код все равно будет работать.
Сейчас мы перейдем к изучению механизмов обработок ошибок - с помощью операторов Try/Catch и функции window.onerror.
window.onerror
Откровенно говоря, функция window.onerror имеет небольшую практическую пользу. Ее чаще всего используют для полного отключения всех сообщений об ошибках во время выполнения:
window.onerror = function(){ return true; }
С этим кодом сообщение об ошибке никогда не будет выводиться. Однако в связи с этим приложение может не работать. Например, если бы переменная была null и с ней была выполнена какая-то операция, то в обычной ситуации должно появиться сообщение об ошибке. При использовании этого кода функция или сценарий в случае возникновения ошибки будет просто молча останавливаться.
Функцию window.onerror можно также использовать для вывода пользователям несколько более дружественных сообщений об ошибках. Можно просто вывести, например, сообщение 'Произошла ошибка, свяжитесь, пожалуйста, с Web-мастером', вместо вывода пользователю всех технических деталей ошибки (что большинство браузеров делает по умолчанию).
Еще одно использование window.onerror состоит в отправке разработчику списка всех ошибок, произошедших на сайте. Можно использовать AJAX для отправки сообщений об ошибках в форме, чтобы можно было позже их исправить. Все это возможно сделать неявно, без взаимодействия с пользователем.
Try/Catch/Finally и Throw
Операторы Try/Catch являются несомненно наиболее распространенным и обычно лучшим способом реализовать обработку ошибок в JavaScript. Но не только это - операторы Try/Catch могут иногда быть единственным способом реализовать некоторые задачи, такие, как обнаружение объекта. Возьмем, например, простую функцию для создания в Internet Explorer объекта XMLHttp:
var activeXObjects = ['Msxml2.XMLHTTP.6.0', 'Msxml2.XMLHTTP.5.0', 'Msxml2.XMLHTTP.4.0', 'Msxml2.XMLHTTP.3.0', 'Msxml2.XMLHTTP', 'Microsoft.XMLHTTP']; for(var i=0; i<activeXObjects.length; i++){ try{ return new ActiveXObject(activeXObjects[i]); }catch(err){} }
Заранее неизвестно, какие объекты установил пользователь, и, к сожалению, браузер не предоставляет никакого механизма это определить. Поэтому остается создавать каждый из 6 возможных объектов, пока один из них (будем надеяться) не заработает.
Операторы Try/Catch можно использовать для перехвата ошибок двух типов: ошибок времени выполнения и ошибок пользователя. Ошибки времени выполнения, как говорилось ранее, возникают, когда у компилятора JavaScript существует проблема с созданным кодом. Ошибки пользователя, с другой стороны, будут технически проходить без проблем, но возникают в связи с контекстом приложения. Если имеется поле, в которое пользователь, например, должен ввести свой возраст, и пользователь вводит -2, то это приводит к появлению ошибки.
Блок Try/Catch имеет достаточно простой синтаксис:
try{ // код }catch(err){ // код обработки ошибки }
Если код в блоке try приводит к ошибке, то сценарий немедленно переходит в блок catch. Объект ошибки " err" в JavaScript имеет ряд полезных свойств - описание, сообщение, имя и номер, которые можно использовать для вывода информации о том, что произошло:
try{ var x; x[5] = 5; }catch(err){ alert('An error occured: '+err.description); }
Если в операторе catch окажется ошибка, то JavaScript сможет обратиться в дальнейшем к ее описанию.
Такой блок кода Try/Catch можно применять в любом месте. Однако, обычно, код должен быть написан таким образом, чтобы это не нужно было использовать, - в частности, весь ввод должен проверяться.
Блок Try/Catch можно применять также для создания своих собственных ошибок:
function setAge(x){ if(typeof(x)=='undefined') throw('Вы должны ввести возраст'); if(typeof(x)!='number') throw('Возраст должен быть числом'); if(x<0) throw('Возраст не может быть меньше 0'); if(x>120) throw('Возраст не может быть больше 120'); var myAge = x; // еще код } try{ setAge(userInput); }catch(err){ alert(err); }
В этом случае выполняется проверка того, что пользователь вводит возраст. Если он вводит недопустимые данные, сценарий немедленно завершается, а пользователь получает сообщение об ошибке.
Блок try/catch имеет еще одну часть, оператор "finally":
try{ // код }catch(err){ // код }finally{ // код }
Код в "завершающем блоке" будет выполняться независимо от того, что происходит с операторами Try/Catch. В чем же разница между завершающим блоком и простым размещением кода после блока try/catch? В большинстве случаев никакой разницы не будет. Однако, если блок try/catch находится в функции и происходит выход из функции в блоке try или catch, то возникнет существенное различие:
function myFunction(){ try{ return someValue; }catch(err){ return defaultValue; }finally{ alert('finally!'); } alert('End!'); }
В этом случае оба блока try и catch возвращают значение. Мы получим сообщение "finally!", но не получим сообщение "End!", потому что произойдет выход из функции до сообщения alert('End!'). То же самое остается справедливым для операторов Try/Catch, которые осуществляют выход из тела цикла for или while, например:
for(var i=0; i<10; i++){ try{ if(i==5) continue; }catch(err){ // обработка ошибки }finally{ // код } // еще код }
Обработка ошибок в AJAX
Запросы XMLHttp, рассмотренные в предыдущей лекции, могут иметь совершенно другой тип ошибки: данные просто не проходят. Это можно проверить через статус объекта XMLHttp:
function processingFunction(){ if(oXml.readyState!=4) return; // запрос не выполнен switch(oXml.status){ case 0: case 200: // запрос выполнен break; case 408: case 504: // запрос превысил время ожидания // код break; default: // ошибка запроса // код return; // возможно, вы захотите выйти break; } // продолжение обработки запроса }
oXml в этом примере является объектом XMLHttp, а функция processingFunction была присоединена к свойству onreadystatechange этого объекта.
Проверяя код статуса, мы узнаем, был ли запрос обработан успешно. Код 200 является в HTTP стандартным кодом статуса "Все прошло нормально" . Код 0 возникает при загрузке файлов из локальной файловой системы (если для этого есть соответствующие полномочия). Статус код 0 часто возникает при локальном тестировании приложения.
Коды статуса 408 и 504 представляют ситуацию с превышением времени ожидания. Очень часто это указывает на сетевые проблемы, и простое повторение запроса может разрешить проблему. Однако отметим, что эти коды представляют также слишком длительную работу сервера над ответом. Например, если существует ошибка сценария на сервере, которая приводит к бесконечному циклу, то может возникнуть код ошибки 408 или 504. В этом случае повторная попытка будет вредоносной, поэтому надо быть осторожным. Самым безопасным является уведомление пользователя и выход из функции, но это не очень корректно по отношению к пользователю.
Все другие коды ошибок имеют свои собственные значения, но в данной ситуации это не важно. Нас интересует только то, что мы не получили нужные данные. Поэтому если код попадает в область "default", то мы имеем проблему. Наверно в этой ситуации лучше всего сообщить пользователю о проблеме и выйти из функции.
Это почти все об обработке ошибок в JavaScript. Имеет смысл включать в функции обработку ошибок, но, возможно, что это не требуется для каждой функции или каждого фрагмента кода. В большинстве ситуаций достаточно проверки ввода пользователей. Для реализации проверки пользователя наиболее полезным средством является использование блоков Try/Catch/Throw.
В следующей лекции будет рассмотрена рекурсия:
"Чтобы понять рекурсию, сначала необходимо понять рекурсию".