11: Обработка ошибок в JavaScript

AJAX

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.

В следующей лекции будет рассмотрена рекурсия:

"Чтобы понять рекурсию, сначала необходимо понять рекурсию".