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

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

function breakPromise() {
    return new Promise((resolve, reject) => {
        reject('something');
    });
}
breakPromise()
    .catch(e => {
        console.log(e);
    });
--------------------------
Result of console.log
something

Рассмотрим приведенный выше фрагмент кода. Мы отклоняем обещание, использующее строку. Оператор console.log в блоке catch просто напечатает ‘something’. Представьте, что вы будете делать, если вам будет поручено исправить ошибку, которая просто выводит такие строки в журнал.

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

function breakPromiseSmart() {
    return new Promise((resolve, reject) => {
        reject(new Error('something bad'));
    });
}

breakPromiseSmart()
    .catch(e => {
        console.log(e);
    });
-----------------------------
Result of console.log
Error: something bad
    at Promise (index.js:3:16)
    at new Promise (<anonymous>)
    at breakPromiseSmart (index.js:2:12)
    at Object.<anonymous> (index.js:7:1)
    at Module._compile (module.js:652:30)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Function.Module.runMain (module.js:693:10)

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

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

Класс ошибки предоставляет некоторые полезные свойства, например

  • сообщение (сообщение об ошибке)
  • name (название ошибки)
  • стек (имя файла, номер строки, стек вызовов функции)

Мы можем сделать наши журналы ошибок намного более полезными, добавив свойства к объектам ошибок, однако лучший способ сделать это - создать собственные классы ошибок, расширив класс Error.

class BadRequestError extends Error {
    constructor(message, meta = {}) {
        super(message);

        Error.captureStackTrace(this, BadRequestError);

        let proto = Object.getPrototypeOf(this);
        proto.name = 'BadRequestError';

        this.meta = meta;
    }

    toString() {
        return `${super.toString()} ${JSON.stringify(this.meta)}`;
    }
}

function breakPromise() {
    return new Promise((resolve, reject) => {
        reject(new BadRequestError('something bad', {
            expected: 'this',
            got: 'that'
        }));
    });
}

breakPromise()
    .catch(e => {
        console.log(e);
    });
--------------------------------
Result of console.log
{ BadRequestError: something bad
    at Promise (index.js:23:16)
    at new Promise (<anonymous>)
    at breakPromise (index.js:22:12)
    at Object.<anonymous> (index.js:30:1)
    at Module._compile (module.js:652:30)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Function.Module.runMain (module.js:693:10) meta: { expected: 'this', got: 'that' } }

Мы видим, что, расширив класс Error для создания BadRequestError, мы смогли добавить гораздо больше информации в трассировку стека и обогатить наши журналы.

Заявление Ошибка .captureStackTrace (this, BadRequestError); Короче говоря, используется для исключения конструктора из трассировки стека.

Если мы создадим несколько таких классов ошибок и выбросим их соответствующим образом, мы сможем вернуть правильные ответы. Например

breakPromise()
    .catch(e => {
        if (e instanceof BadRequestError) {
            res.status(400);
        } else if (e instanceof SomeotherError) {
            res.status(412);
        } else {
            res.status(500);
        }
        return res.send({
            message: e.message,
            error: e.meta
        });
    });

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

Заявление об ограничении ответственности: не обращайте внимания на некоторые утверждения, в которых я сделал слишком простые обобщения.

Полезные ссылки: