Ошибки в JavaScript

Feb 24, 2019

throw new Error

Указываем на ошибку в коде

throw new Error('произошло что-то плохое');

Код выше создаст экземпляр объекта Error и будет сгенерировано (выброшено) исключение с этим объектом. Инструкция throw может генерировать исключения, содержащие произвольные выражения. При этом выполнение скрипта остановится в том случае, если не были предприняты меры по обработке ошибки.

Свойства message экземпляра Error

let customError = throw new Error('произошло что-то плохое');
console.log(customError.message) // 'произошло что-то плохое'

Свойства stack экземпляра Error

Свойство stack позволяет просмотреть стек вызовов (историю ошибки), который показывает последовательность операций, приведшую к неправильной работе программы

try...catch

Блок try...catch представляет собой самый простой способ обработки ошибок. Часто применятеся для обработки ошибок в конструкциях async/await.

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

let b = 11;
try {
    console.log(d); // d не объявлена - возникнет ошибка
} catch(error) {
    console.log(error); // console выведет сообщение с ошибкой и стек ошибки
}
console.log(a); // a будет выведена, то есть работа скрипта не остановится

При отсутствии блока try...catch выполнение скрипта было бы остановлено.

finally

Необязательный блок finally позволяет выполнить некий код независимо от того, произошла ошибка или нет.

let b = 11;
try {
    console.log(d); // d не объявлена - возникнет ошибка
} catch(error) {
    console.log(error); // console выведет сообщение с ошибкой и стек ошибки
} finally {
    console.log(a); // код будет выполнен всегда
}

Асинхронные операции - callback

Стоит знать - если в асинхронной функции возникает ошибка, скрипт продолжит выполняться. Если асинхронность реализуется на callback, то такой callback обычно получает два параметра:
err - содержат ошибку,
result - результ выполнения асинхронной операции

request({
    url: url_1,
    encoding: 'binary',
    method: 'GET'
}, function (error, response, body) {
    if (error) {
        reject(error);
    }

Этим мы исключаем возможность того, что result может иметь значение undefined.

Асинхронные операции - promise

Блок catch перехватывает все ошибки, произошедшие в промисах, которые находятся до него, или все ошибки, которые произошли в коде после предыдущего блока catch.

Promise.resolve(1)
    .then(res => {
        console.log(res) // 1

        throw new Error('something went wrong')

        return Promise.resolve(2)
    })
    .then(res => {
        console.log(res) // не будет выполнен
    })
    .catch(err => {
        console.error(err) // мы увидим, что тут делать, позже
        return Promise.resolve(3)
    })
    .then(res => {
        console.log(res) // 3
    })
    .catch(err => {
        // это на случай, если в предыдущем блоке возникнет ошибка
        console.error(err)
})

Асинхронные операции - try...catch

async/await использует классический способ обработки ошибок - try...catch...finally.

;(async function() {
    try {
        await someFuncThatThrowsAnError()
    } catch (err) {
        console.error(err) // рассмотрим позже
    }

    console.log('Easy!') // будет выполнено
})()

Генерируем ошибки в серверном коде

Далее будет показан подход с использованием собственного конструктора для экземпляров объекта Error и кодов ошибок, которые удобно передавать во фронтенд или любым механизмам, использующим серверные API.

Что нам предстоит реализовать:

Универсальная обработка ошибок -  базовый логика, подходящая для обработки любых ошибок, в ходе работы которой выдаётся сообщение вида Something went wrong, please try again or contact us.

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

Кастомный конструктор объектов ошибок

Расширим стандартный класс Error. Расширяя стандартный класс Error, мы получаем возможности по трассировке стека.

Добавим в наш кастомный объект ошибки два свойства. Свойство code (err.code) и свойство status. В свойство status будет записываться код состояния HTTP-запроса.

class CustomError extends Error {

    constructor(code = 'GENERIC', status = 500, ...params) {

        super(...params);

        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, CustomError)
        }

        this.code = code;
        this.status = status;
    }
}

module.exports = CustomError;

Маршрутизация

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

Для того чтобы справиться с этой проблемой, мы можем реализовать кастомный обработчик маршрутов и определять логику маршрутов в виде обычных функций. Благодаря такому подходу, если функция маршрута выбрасывает ошибку, она попадёт в обработчик маршрутов, который затем может передать её клиентской части приложения. При возникновении ошибки на сервере мы планируем передавать её во фронтенд в следующем формате (основываясь на JSON API):

{
    error: 'SOME_ERROR_CODE',
    description: 'Something bad happened. Please try again or     contact support.'
}

Кастомный обработчик маршрутов:

const express = require('express')
const router = express.Router()
const CustomError = require('../CustomError')

router.use(async (req, res) => {
    try {

        const route = require(`.${req.path}`)[req.method]

        try {
            const result = await route(req); // передаем запрос функции route
            res.send(result); // передаем клиенту то, что получено от функции route
        } catch (err) {
            /*
            Здесь мы окажемся, если error произошла в route функции
            */
            if (err instanceof CustomError) {
                /*
                Если ошибка уже обработана, трансформируем ее в возвращаемый объект
                */
                return res.status(err.status).send({
                    error: err.code,
                    description: err.message,
                })
            } else {
                console.error(err); // для отладочных целей

                // Ошибка необработана - возвращаем универсальный объект ошибки
                return res.status(500).send({
                    error: 'GENERIC',
                    description: 'Something went wrong. Please try again or contact support.',
                })
            }
        }
    } catch (err) {
        /* 
        Здесь мы окажемся, если запрос неудачен, то есть либо имя файла
        запроса отсутствует или отсутствует экспортированная функция
        с переданным методом запроса
        */
        res.status(404).send({
            error: 'NOT_FOUND',
            description: 'The resource you tried to access does not exist.',
        })
    }
})

module.exports = router

Файл маршрутов:

const CustomError = require('../CustomError')

const GET = req => {
    // успешный запрос (пример создан для асинхронности)
    return Promise.resolve({ name: 'Rio de Janeiro' })
}

const POST = req => {
    // пример универсальной ошибки
    throw new Error('Some unexpected error, may also be thrown by a library or the runtime.')
}

const DELETE = req => {
    // пример кастомной ошибки
    throw new CustomError('CITY_NOT_FOUND', 404, 'The city you are trying to delete could not be found.')
}

const PATCH = req => {
    // пример захвата ошибок и перевода в CustomError
    try {
        // что-то плохое случилось здесь
        throw new Error('Some internal error')
    } catch (err) {
        console.error(err) // определяем, что делать

        throw new CustomError(
            'CITY_NOT_EDITABLE',
            400,
            'The city you are trying to edit is not editable.'
        )
    }
}

module.exports = {
    GET,
    POST,
    DELETE,
    PATCH,
}

запрос GET /city попадёт в функцию const GET = req =>...,
запрос POST /city попадёт в функцию const POST = req =>...
и т.д.

Как итог: при обработке ошибок во фронтенд может попасть либо общая ошибка, содержащая лишь предложение попробовать снова или связаться с владельцем сервера, либо ошибка, сформированная с использованием конструктора CustomError, которая содержит подробные сведения о проблеме.

Универсальная ошибка:

{
    error: 'GENERIC',
    description: 'Something went wrong. Please try again or contact support.'
}

Генерируем CustomError:

throw new CustomError('MY_CODE', 400, 'Error description')

Деформируется в:

{
    error: 'MY_CODE',
    description: 'Error description'
}

Репозиторий с кодом.

Генерируем ошибки на клиенте

На что стоит обратить внимание - ошибки одного типа нужно показывать в одном стиле.

  1. Глобальные ошибки — например, ошибки общего характера, приходящие с сервера, или ошибки, которые возникают в том случае, если пользователь не вошёл в систему и т.д.
  2. Специфические ошибки, приходящие от сервера. Например, если пользователь попытался войти в систему и отправил на сервер имя и пароль, а сервер сообщил ему о том, что пароль неправильный. Сообщения о таких ошибках приходят с сервера.
  3. Специфические ошибки, возникающие на клиенте. Например, сообщение о некорректном адресе электронной почты, введённом в соответствующее поле.

Далее приведен разбор того, как данные ошибки реализованы на react.

 

Добавить комментарий