Errors


Приложения, запущенные в Node.js обычно имеют четыре типа ошибок:

  • Стандартные ошибки JavaScript, например:
  • >EvalError<: выпадает, когда не удается вызов eval()
  • >SyntaxError<: выпадает в ответ на неправильный синтаксис JavaScript
  • >RangeError<: выпадает, когда значение не входит в заданный диапазон
  • >ReferenceError<: выпадает, когда используются неопределенные переменные
  • >TypeError<: выпадает, когда переданные аргументы имеют неправильный тип
  • >URIError<: выпадает, когда не используется глобальная URI функция-обработчик
  • Системные ошибки, вызванные ограничениями предустановленной ОС, такие, как попытки открыть файл, которого не существует, отправить данные через закрытый сокет и т.п.
  • Ошибки, заданные пользователем в коде приложения
  • Assertion ошибки попадают в отдельный класс ошибок, которые выападают тогда, когда Node.js обнаруживает грубое нарушение логики, чего быть не должно. Эти ошибки генерируются модулем assert.

Все системные и JavaScript ошибки, выданные Node.js, наследуются или являются экземплярами стандартного класса JavaScript >Error< и гарантированно предоставляют как минимум свойства, доступные в этом классе.

Перехват и распространение ошибок

Node.js поддерживает некоторые механизмы распространения и обработки ошибок, которые выпали во время работы приложения. То, как обрабатываются эти ошибки и как по ним составляется отчет, полностью зависит от типа ошибки и API.

Все ошибки JavaScript обрабатываются как исключения, которые сразу генерируют и выдают ошибку, используя стандартный механизм JavaScript throw. Они обрабатываются посредством try / catch, который встроен в JavaScript.


// Throws with a ReferenceError because z is undefined
try {
  const m = 1;
  const n = m + z;
} catch (err) {
  // Handle the error here.
}

Использование механизма JavaScript throw порождает исключение, которое нужно обработать try / catch, или же процесс Node.js будет закрыт.

За некоторыми исключениями, синхронные API (любой блокирующий метод, который не принимает функцию callback, такой, как fs.readFileSync) будет использовать throw для составления отчетов об ошибках.

Отчет об ошибках, которые были обнаружены в асинхронных API, может составляться несколькими способами:

  • Большинство асинхронных методов, которые принимают функцию callback, принимают также объект Error, переданный в качестве первого аргумента в эту функцию. Если этот первый аргумент не имеет значения null и является экземпляром Error, тогда ошибка должна быть обработана.
  • 
    const fs = require('fs');
    fs.readFile('a file that does not exist', (err, data) => {
      if (err) {
        console.error('There was an error reading the file!', err);
        return;
      }
      // Otherwise handle the data
    });
    
  • Когда на объект EventEmitter вызывается асинхронный метод, ошибки направляются в событие 'error' этого объекта.
  • 
    const net = require('net');
    const connection = net.connect('localhost');
    
    // Adding an 'error' event handler to a stream:
    connection.on('error', (err) => {
      // If the connection is reset by the server, or if it can't
      // connect at all, or on any sort of error encountered by
      // the connection, the error will be sent here.
      console.error(err);
    });
    
    connection.pipe(process.stdout);
    
  • Обработка типичных асинхронных методов в API Node.js подразумевает использование механизма throw для создания исключений, которые должны обработываться try / catch. Полного списка таких методов не существует; следует ссылаться на документацию к каждому методу для определения механизма обработки ошибок.

Использование механизма событий 'error' является основным для API, основанных на стримах и эмитерах событий, которые предоставляют собой серию асинхронных операций во времени (в противоположность единственной операции, которая может быть или удачной, или нет).

Для всех объектов EventEmitter актуально следующее: если нет обработчика событий 'error', выпадает ошибка, вследствие которой процесс Node.js отчитывается о необработом исключении и "ломается"; в противном случае используется модуль domain или регистрируется обработчик для события process.on('uncaughtException').


const EventEmitter = require('events');
const ee = new EventEmitter();

setImmediate(() => {
  // This will crash the process because no 'error' event
  // handler has been added.
  ee.emit('error', new Error('This will crash'));
});

Ошибки, сгенерированные таким способом, не могут перехватываться посредством try / catch, так как они выпадают уже после того, как код вызова был закрыт.

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

Функции обратного вызова в стиле Node.js

Большинство асинхронных методов, открытых для основого API Node.js реализованы по шаблону, который называется "Функции обратного вызова в стиле Node.js". Следуя этому шаблону, функция обратного вызова передается методу как аргумент. Когда операция выполнена, либо звершилась с ошибкой, функция обратного вызова вызывается с объектом Error, переданным в качестве первого аргумента. Если нет ошибки, первый аргумент будет null.


const fs = require('fs');

function nodeStyleCallback(err, data) {
 if (err) {
   console.error('There was an error', err);
   return;
 }
 console.log(data);
}

fs.readFile('/some/file/that/does-not-exist', nodeStyleCallback);
fs.readFile('/some/file/that/does-exist', nodeStyleCallback)

Механизм JavaScript try / catch не может быть использован для перехвата ошибкой, сгенерированных асинхронным API. Наиболее часто встречающаяся ошибка начинающих – попытка использовать throw внутри функции обратного вызова в стиле Node.js:


// THIS WILL NOT WORK:
const fs = require('fs');

try {
  fs.readFile('/some/file/that/does-not-exist', (err, data) => {
    // mistaken assumption: throwing here...
    if (err) {
      throw err;
    }
  });
} catch(err) {
  // This will not catch the throw!
  console.log(err);
}

Это не будет работать, потому что функция обратного вызова, которая передается fs.readFile(), вызывается асинхронно. На тот момент, как была вызвана функция обратного вызова, остальной код (включая блок try { } catch(err) { }) уже был закрыт. Выпадение ошибки анутри функции обратного вызова может уничтожить процесс Node.js в большинстве случаев. Если включены домены, или обработчик был зарегистрирован с process.on('uncaughtException'), подобные ошибки можно перехватить.

Class: Error

Основной объект Error в JavaScript не указываает на определенное обстоятельство, почему случилась ошибка. Объекты Error ловят "трассировку стека", детализированную контрольной точкой в коде, в которой была подтверждена ошибка, и могут предоставить тестовое описание ошибки.

Все ошибки, сгенерированные Node.js, включая все системный и JavaScript ошибки, будут либо экземплярами класса Error, либо будут наследоваться из этого класса.

new Error(message)

Создает новый объект Error и устанавливает свойство error.message в текстовое сообщение. Если объект передается как message, текстовое сообщение генерируется вызовом message.toString(). Свойство error.stack представляет контрольную точку в коде, в которой вызывается new Error(). Трассировка стека зависит от V8's stack trace API. Трассировки стека распространяются только на: (а) начало выполнения синхронного кода и (б) на число фреймов, заданных свойством Error.stackTraceLimit, в зависимости от того, что имеет меньшее значение.

Error.captureStackTrace(targetObject[, constructorOpt])

Создает свойство .stack в targetObject, который, при успешном допуске, возвращает строку, которая содержит в себе место в коде, где вызывается Error.captureStackTrace()


const myObject = {};
Error.captureStackTrace(myObject);
myObject.stack  // similar to `new Error().stack`

Первая строка трассировки, вместо приставки ErrorType: message, будет результатом вызова targetObject.toString().

Опициональный аргумент constructorOpt принимает функцию. Если это задано, все фреймы над constructorOpt, включая сам constructorOpt, не будут внесены в сгенерированный стек трассировки.

Аргумент constructorOpt удобно использовать для того, чтобы скрыть детали реализации или генерацию ошибок от конечного пользователя. Например:


function MyError() {
  Error.captureStackTrace(this, MyError);
}

// Without passing MyError to captureStackTrace, the MyError
// frame would show up in the .stack property. By passing
// the constructor, we omit that frame and all frames above it.
new MyError().stack

Error.stackTraceLimit

Свойство Error.stackTraceLimit определяет количество фреймов стека, собранных трассировкой стека (вне зависимости от того, были ли они сгенерированы new Error().stack или Error.captureStackTrace(obj)).

Значение по умолчанию – 10, но может быт любым валидным числом JavaScript. Изменения повлияют на любую трассировку стека, которая появилась после того, как было изменено значение.

Если задать нечисловое значение или отрицательное число, трассировки стека не будут захватывать фреймы.

error.message

Возвращает описание ошибки в виде строки, как задано в new Error(message). message передается в конструктор и также появляется в первой строке трассировки стека Error, однако, изменение этого свойства после создания объекта Error может и не повлиять на первую строку трассировки.


const err = new Error('The message');
console.log(err.message);
  // Prints: The message

error.stack

Возвращает строку, которая содержит описание контрольной точки в коде, в которой размещен Error.

Например:


Error: Things keep happening!
   at /home/gbusey/file.js:525:2
   at Frobnicator.refrobulate (/home/gbusey/business-logic.js:424:21)
   at Actor. (/home/gbusey/actors.js:400:8)
   at increaseSynergy (/home/gbusey/actors.js:701:6)

Первая строка форматируется как >error class name<: >error message< и сопровождается серией фреймов стека (каждая строка начинается с "at"). Каждый фрейм описывает тот момент в коде, который привел к генерации ошибки. V8 пытается отобразить имя каждой функции (по имени переменной, имени функции или имени объекта метода), но иногда ему не удается найти подходящее имя. Если V8 не может определить имя функции, отображается только информация о местонахождении этого фрейма. В прочих случаях, опереденное имя функции оотображается с информацией о местонахождении в скобках.

Важно то, что фреймы генерируются только функциями JavaScript. Если, к примеру, синхронное выполнение передает через аддон С++ функцию, вызванную cheetahify, которая сама вызывает функцию JavaScript. фрейм делает так, что cheetahify не будет отображаться в трассировке стека:


const cheetahify = require('./native-binding.node');

function makeFaster() {
  // cheetahify *synchronously* calls speedy.
  cheetahify(function speedy() {
    throw new Error('oh no!');
  });
}

makeFaster(); // will throw:
  // /home/gbusey/file.js:6
  //     throw new Error('oh no!');
  //           ^
  // Error: oh no!
  //     at speedy (/home/gbusey/file.js:6:11)
  //     at makeFaster (/home/gbusey/file.js:5:3)
  //     at Object. (/home/gbusey/file.js:10:1)
  //     at Module._compile (module.js:456:26)
  //     at Object.Module._extensions..js (module.js:474:10)
  //     at Module.load (module.js:356:32)
  //     at Function.Module._load (module.js:312:12)
  //     at Function.Module.runMain (module.js:497:10)
  //     at startup (node.js:119:16)
  //     at node.js:906:3

Информация о местонахождении может быть:

  • native, если фрейм представляет собой внутренний вызов V8 (как в [].forEach)
  • plain-filename.js:line:column, если фрейм представляет собой внутренний вызов Node.js
  • /absolute/path/to/file.js:line:column, если фрейм представляет собой вызов в пользовательской программе или его зависимости.

Строка, отображающая трассировку стека, генерируется, когда свойство error.stack получает доступ.

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

Ошибки системного уровня генерируются как дополненные экземпляры Error.

Class: RangeError

Подкласс Error, который отображает, что аргумент не входит в настройки (вне настроек опций для параметра заданной функции) или диапазон доступных значений для функции (например, числовой).

К примеру:

require('net').connect(-1);
  // throws RangeError, port should be > 0 && > 65536

Class: ReferenceError

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

Пока клиентский код может генерировать и распространять эти ошибки, на практике это делает только V8:


doesNotExist;
  // throws ReferenceError, doesNotExist is not a variable in this program.

Экземпляры ReferenceError имеют свойство error.arguments, чье значение является массивом, состоящим из одного элемента: строки, представляющей собой ту самую неопределенную переменную.


const assert = require('assert');
try {
  doesNotExist;
} catch(err) {
  assert(err.arguments[0], 'doesNotExist');
}

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

Class: SyntaxError

Подкласс Error, который показывает, что программа не валидна для JavaScript. Эти ошибки генерируются и распространяются только в результате оценки кода. Оценка кода может быть вызвана eval, Function, require или vm. Эти ошибки почти всегда являются индикаторами неправильно работающей программы.


try {
  require('vm').runInThisContext('binary ! isNotOk');
} catch(err) {
  // err will be a SyntaxError
}

Экземпляры SyntaxError не восстанавливаются в контесте, в котором были созданы. Они могут только улавливаться другим контекстом.

Class: TypeError

Подкласс Error, который показывает, что аргумент имеет недопустимый тип. Например, передается функция в параметр, который ожидает строку. Это будет рассматриваться, как TypeError.


require('url').parse(() => { });
  // throws TypeError, since it expected a string

Node.js генерирует и выдает экземпляры TypeError непосредственно как форму валидации аргумента.

Исключения и ошибки

Исключение JavaScript – это значение, которое получается в результате некорректной операции или выражения throw. Если не требуется, чтобы эти значения были экземплярами класса Error или классами, наследуемыми от Error, все исключения, выданные Node.js или JavaScript, будут экземплярами Error.

Некоторые исключения являются невосстанавливаемыми в JavaScript. Некоторые всегда крашат процесс Node.js. Примеры включают в себя проверки assert() или вызовы abort() на стороне С++.

Системные ошибки

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

Системные ошибки обычно генерируются на уровне системных вызовов: полный список кодов ошибок и их значений доступен после запуска man 2 intro или man 3 errno на большинстве Unix-систем, или же онлайн.

В Node.js системные ошибки представляют собой объекты Error с дополнительными свойствами.

Class: System Error

error.code

error.errno

Возвращает строку, содержащую код ошибки, которая всегда представляет собой E с последующим набором заглавных букв и может ссылаться на man 2 intro.

Свойства error.code и error.errno дополняют друг друга и возвращают одинаковое значение.

error.syscall

Возвращает строку, описывающую неудавшийся системный вызов.

Основные системные ошибки

Этот список не является полным, но содержит в себе большую часть основных системных ошибок, с которыми девелопер чаще всего встречается при написании программы на Node.js.

  • EACCES (Отказано в доступе): Попытка получить доступ к файлу, который был запрещен правами доступа.
  • EADDRINUSE (Адрес уже используется): Попытка привязать сервер (net, htttp, https) к локальному адресу прошла неудачно по причине того, что другой сервер на локальной системе уже занимает этот адрес.
  • ECONNREFUSED (В соединении отказано): Соединение не может быть произведено, так как целевой компьютер активно отклоняет его. Обычно это является результатом попыток подсоединиться к неактивному серверу на чужом хосте.
  • ECONNRESET (Соединение сброшено пиром): Соединение принудительно закрыто пиром. Обычно является результатом потери подключения на удаленном сокете по причине timeout или reboot. В основном встречается на модулях http и net.
  • EEXIST (Файл существует): В ходе выполнения операции был получен существующий файл, когда требовалось получить несуществующий.
  • EISDIR (Это директория): Операция ожидала файл, но данный путь является путем к директории.
  • EMFILE (Слишком много открытых файлов в системе): Набрано максимальное количество файловых дескрипторов, разрешенных системой, запросы к другим дескрипторами не могут быть выполнены, пока хотя бы один не будет закрыт. Такое случается при открытии большого количества файлов одновременно, особенно на системах (например, OS X), которые имеют малый лимит на файловые дескрипторы для процессов. Для настройки лимита, запустите ulimit -n 2048 в той же оболочке, где запускается процесс Node.js.
  • ENOENT (Нет такого файла или директории): Обычно выпадает вследствие операций fs для отображения, что копмонент заданного пути не существует – файл или директория не могут быть найдены по заданному пути.
  • ENOTDIR (Не является директорией): Компонент заданного пути существует, но не является директорией. Часто выпадает из fs.readdir.
  • ENOTEMPTY (Директория не является пустой): Целью операции была пустая директория, а была получена не пустая (часто fs.unlink).
  • EPERM (Операция не разрешена): Попытка произвести операцию, которая требует особых прав на доступ.
  • EPIPE (Проблемы с каналом): Запись в канал, сокет или FIFO, в которых нет процесса для чтения данных. Обычно встречается на стороне net и http, показывая, что удаленная стороны стрима, в которую идет запись, закрыта.
  • ETIMEDOUT (Вышло время выполнения операции): Подключение или отправка запроса не удались по причине того, что сторона, к которой идет подключение, не отвечает. Встречается в net или http, часто сигнализирует о том, что socket.end() был вызван неправильно.