Выбрать главу

Допустим, у вас есть функция promptInteger, которая запрашивает целое число и возвращает его. Что она должна сделать, если пользователь введёт «апельсин»?

Один из вариантов – вернуть особое значение. Обычно для этих целей используют null и undefined.

function promptNumber(question) {

  var result = Number(prompt(question, ""));

  if (isNaN(result)) return null;

  else return result;

}

console.log(promptNumber("Сколько пальцев видите?"));

Это надёжная стратегия. Теперь любой код, вызывающий promptNumber, должен проверять, было ли возвращено число, и если нет, как-то выйти из ситуации – спросить снова, или задать значение по-умолчанию. Или вернуть специальное значение уже тому, кто его вызвал, сообщая о неудаче.

Во многих таких случаях, когда ошибки возникают часто и вызывающий функцию код должен принимать их во внимание, совершенно допустимо возвращать специальное значение как индикатор ошибки. Но есть и минусы. Во-первых, что, если функция и так может вернуть любой тип значения? Для неё сложно найти специальное значение, которое будет отличаться от допустимого результата.

Вторая проблема – работа со специальными значениями может замусорить код. Если функция promptNumber вызывается 10 раз, то надо 10 раз проверить, не вернула ли она null. Если реакция на null заключается в возврате null на уровень выше, тогда там, где вызывался этот код, тоже нужно встраивать проверку на null, и так далее.

Исключения

Когда функция не может работать нормально, мы бы хотели остановить работу и перепрыгнуть туда, где такая ошибка может быть обработана. Этим занимается обработка исключений.

Код, встретивший проблему в момент выполнения, может поднять (или выкинуть) исключение (raise exception, throw exception), которое представляет из себя некое значение. Возврат исключения напоминает некий «прокачанный» возврат из функции – он выпрыгивает не только из самой функции, но и из всех вызывавших её функций, до того места, с которого началось выполнение. Это называется развёртыванием стека (unwinding the stack). Может быть, вы помните стек функций из главы 3… Исключение быстро проматывает стек вниз, выкидывая все контексты вызовов, которые встречает.

Если бы исключения сразу доходили до самого низа стека, пользы от них было бы немного. Они бы просто предоставляли интересный способ взорвать программу. Их сила в том, что на их пути в стеке можно поставить «препятствия», которые будут ловить исключения, мчащиеся по стеку. И тогда с этим можно сделать что-то полезное, после чего программа продолжает выполняться с той точки, где было поймано исключение.

Пример:

function promptDirection(question) {

  var result = prompt(question, "");

  if (result.toLowerCase() == "left") return "L";

  if (result.toLowerCase() == "right") return "R";

  throw new Error("Недопустимое направление: " + result);

}

function look() {

  if (promptDirection("Куда?") == "L")

    return "дом";

  else

    return "двух разъярённых медведей";

}

try {

  console.log("Вы видите", look());

} catch (error) {

  console.log("Что-то не так: " + error);

}

Ключевое слово throw используется для выбрасывания исключения. Ловлей занимается кусок кода, обёрнутый в блок try, за которым следует catch. Когда код в блоке try выкидывает исключение, выполняется блок catch. Переменная, указанная в скобках, будет привязана к значению исключения. После завершения выполнения блока catch, или же если блок try выполняется без проблем, выполнение переходит к коду, лежащему после инструкции try/catch.

В данном случае для создания исключения мы использовали конструктор Error. Это стандартный конструктор, создающий объект со свойством message. В современных окружениях JavaScript экземпляры этого конструктора также собирают информацию о стеке вызовов, который был накоплен в момент выкидывания исключения – так называемое отслеживание стека (stack trace). Эта информация сохраняется в свойстве stack, и может помочь при разборе проблемы – она сообщает, в какой функции случилась проблема и какие другие функции привели к данному вызову.

Обратите внимание, что функция look полностью игнорирует возможность возникновения проблем в promptDirection. Это преимущество исключений – код, обрабатывающий ошибки, нужен только в том месте, где происходит ошибка, и там, где она обрабатывается. Промежуточные функции просто не обращают на это внимания.