Vue обработка ошибок сервера


сентябрь
17
, 2019

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

— добавлять товары с пустыми названиями

— прописывать цены строками

— копипастить портянки текста в описания, хотя мы четко написали в инструкции — не больше 200 символов.

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

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

Валидация на клиенте и сервере. В чем разница?

Валидация на клиенте — это те ошибки, которые мы можем проверить средствами браузера. Например,

— юзер пытается добавить товар с пустым названием

— юзер забыл указать цену товара

— юзер вбивает длинное описание, хотя допускается максимум 200 символов.

Все это можно проверить в клиентской части приложения с помощью javascript.

Валидация на сервере — это все то, что можно проверить на клиенте, плюс некоторые специфичные вещи, часто связанные с базой. Например,

— юзер пытается добавить уже существующий товар

— юзер пытается удалить категорию, хотя в ней есть какие-то товары

— юзер пытается удалить бренд, но у него нет прав доступа на это

Все эти вещи проверяются на бекенде, клиент о них не знает.

Отмечу 2 момента:

— стоит по максимуму валидировать данные на клиенте

— дублировать эти же проверки на сервере.

Зачем это нужно?

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

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

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

Валидация в нашей админке интернет-магазина

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

В нашей админке мы не будем стелить соломку по всему коду, смысл уроков не в этом. Мы подробно рассмотрим валидацию только в одном компоненте, но покроем и клиентские, и серверные проверки.
И научимся показывать юзерам ошибки в человеческом виде.

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

— не пустой ли бренд

— не превышает ли бренд 20 символов (практического смысла мало, но для примера нормально)

— существует ли этот бренд в базе

— нет ли ошибки в запросе (неверный роутер, проходили в третьем уроке)

— общие ошибки, например, сервис недоступен, бекенд лежит

Первые 2 проверки будем делать на клиенте, следующие 2 — на сервере. Пятая — это все невошедшие ошибки, например, упавший ajax-запрос или 500-ка от сервера.

Дублировать клиентские проверки на сервере мы не будем. Они очень простые и легко реализуются на php, не будем перегружать код.
Другие серверные ошибки, номер 3 и 4, уже реализованы в третьем уроке, когда мы готовили api для админки.

Общая схема обработки ошибок

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

Чтобы понимать, какая именно ошибка произошла, добавим такое понятие — код ошибки. Например, на скриншоте код «brand_exists», что означает, бренд уже существует.
Это проверка серверная, но красное сообщение об этом не знает.
Код ошибки — единственное, на что будет ориентироваться это сообщение и все равно, откуда он придет, при клиентской валидации или с ответом от сервера.
Тексты сообщений мы будем брать из отдельного конфига. Теперь давайте сделаем это на практике.

Валидация на клиенте

Откроем компонент BrandsNew.vue и добавим новое поле errorCode, в раздел data. Это тот самый код, который определяет наличие ошибки. По умолчанию — пустая строка. То есть вот так

    data () {
        return {
            visible: false,
            newBrand: '',
            // Новое поле
            errorCode: ''
        }
    }

Добавим еще вычисляемое логическое поле isError, которое покажет, если ли в данный момент ошибка

    computed: {
        isError () {
            return this.errorCode !== '';
        }
    }

Дальше нам нужно вывести сообщение об ошибки в окне добавления бренда. Добавим такой код между инпутом-брендом и кнопкой Добавить

    Ошибка: {{errorCode}}

Это выделенный красным текст, пример взяли из библиотеки minicss. Напоминаю, наше приложение на этом css-фреймворке. Чуток поправим стили, добавим в style

    mark {
        display: block;
        margin: 5px 0 10px 5px;
    }

Но вернемся к разметке. v-if=»isError» означает, что сообщение будем выводить только, когда есть ошибка, логично. А в тексте сообщения выведем пока код ошибки — {{errorCode}}.
Сначала убедимся, что наша схема работает, а потом заменим бездушный errorCode нормальным текстом.

Итак, errorCode у нас есть, нужны методы для работы с ним. Один метод будет устанавливать код, другой очищать. Добавим в раздел methods

    setError (errorCode) {
        this.errorCode = errorCode;
    },
    clearError () {
        this.errorCode = '';
    }

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

Базовая подготовка сделана, пора заняться самой валидацией. Напомню, мы будем проверять, что новый бренд не пустой и что не длиннее 20 символов.
Метод валидации будет выполнять эти проверки и в случае ошибкок устанавливать соответствующий код codeError. Вот так выглядит метод validate

    validate (brand) {
        if (brand === '') {
            this.setError('brand_empty');
            return false;
        }
        if (brand.length > 20) {
            this.setError('brand_long_title');
            return false;
        }
    
        // Если валидация прошла успешно
        this.clearError();
        return true;
    }

Два простых if-а, которые проставляют нужные коды brand_empty или brand_long_title. А в случае успеха дергаем метод clearError, чтобы сбросить ошибку, если она была до этого.
Например, юзер ввел правильно инфу со второго раза.

Важный момент. Помимо установки codeError мы возвращаем true или false в зависимости от итога валидации.
Нам это нужно, чтобы понять, отправлять ли запрос на сервер с добавлением бренда или же ждать, пока юзер введет бренд правильно.

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

    addBrand () {
        this.$store.dispatch('brands/addBrand', this.newBrand);
        this.newBrand = '';
        this.closeModal();
    }

Все просто, вызываем действие через dispatch, очищаем инпут с названием и закрываем модалку. Этот код нужно чуть переделать, вызывать действие только если валидация прошла успешно.
Добавим условие if

    addBrand () {
        if (this.validate(this.newBrand)) {
            this.$store.dispatch('brands/addBrand', this.newBrand);
            this.newBrand = '';
            this.closeModal();
        }
    }

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

Валидация на сервере

С серверными проверками есть свои особенности.

Во-первых, на клиенте мы генерили коды ошибок сами, а для серверных нужно дождаться ответа и получить их из респонса.
Да, мы эти коды (brand_exists и invalid_router) сами сделали в уроке по API. Но вообще мы можем даже и не знать, что за коды нам возвращает бекенд.

Во-вторых, есть интерфейсный момент. Сейчас в методе addBrand сразу после dispatch мы очищаем поле бренда и закрываем модалку.
Это годится, если бренд успешно добавлен и никуда не годится, если сервер вернул ошибку.
Значит, мы должны не просто дернуть dispatch, а дождаться ответа от сервера и только потом решать, что делать.
Если ответ 200 и бренд добавлен, то закрывать модалку, а если ошибка, то выводить ее и оставлять модалку.

Если бы мы писали приложение на jquery, то сделали бы примерно так

    $.ajax({
        url: '',
        data: '',
        success: function() {
            // очищаем поле бренда и закрываем модалку
        },
        error: function() {
            // парсим код ошибки и выводим красное сообщение в модалке
        }
    });

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

Начнем с действия addBrand. Открываем файл store/modules/brands.js и смотрим, как реализовано сейчас

    addBrand (context, newBrand) {
        const data = 'title=' + newBrand;

        axios
            .post('/admin/api/v1/brands', data)
            .then(response => {
                context.commit('ADD_BRAND', response.data)
            });
    }

Отправляем запрос на сервер через axios и в случае успеха вызываем мутацию ADD_BRAND. Нам нужно сделать то же самое, только при этом еще и возвращать промис. Вот так

    addBrand (context, newBrand) {
        const data = 'title=' + newBrand;

        return new Promise((resolve, reject) => {
            axios
                .post('/admin/api/v1/brands', data)
                .then(response => {
                    context.commit('ADD_BRAND', response.data);
                    resolve(response);
                }, error => {
                    reject(error);
                });
        });
    }

Это стандартная заготовка для промисов. resolve и reject — это функции, которые выполняются соответственно при успехе и ошибке.
А что именно делают эти функции, будем решать уже в другом месте — опять в компоненте BrandsNew, в методе addBrand. Немного изменим код.

Было

    addBrand () {
        if (this.validate(this.newBrand)) {
            this.$store.dispatch('brands/addBrand', this.newBrand);
            this.newBrand = '';
            this.closeModal();
        }
    }

Стало

    addBrand () {
        if (this.validate(this.newBrand)) {
            this.$store.dispatch('brands/addBrand', this.newBrand).then(
                response => {
                    this.newBrand = '';
                    this.closeModal();
                },
                error => {
                    let resp = error.response;
                    let errorCode = (resp && resp.data && resp.data.code) ? resp.data.code : 'unknown_error';
                    this.setError(errorCode);
                });
        }
    }

Кода прибавилось, но немного. В метод resolve попадает

    response => {
        this.newBrand = '';
        this.closeModal();
    }

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

    error => {
        let resp = error.response;
        let errorCode = (resp && resp.data && resp.data.code) ? resp.data.code : 'unknown_error';
        this.setError(errorCode);
    });

Здесь параметр error уже нужен — это ответ от сервера, который нужно распарсить и вытащить код ошибки. Разберем по порядку. В первой строке пишем в переменную resp ответ от сервера.
Дальше идет диковатая конструкция resp && resp.data && resp.data.code. Зачем так сложно?
Ведь мы возвращаем ответ с бекенда в виде { code: ‘код ошибки’, … }, то есть поле code там будет всегда. Не совсем.
Код будет, но только для тех случаев, когда наш бекенд нормально обработает ошибку. Например, найдет, что бренд уже существует, или невалидный роутер (ошибка в запросе, в урле).
Если же бекенд просто лежит и вернет нам 500-ку, то в error.response может прийти null или пустая строка. Поэтому resp.data.code выкинет нам ошибку в консоль и поломает работу.

Поэтому именно здесь мы подстилаем соломку и проверяем всю цепочку error.response.data.code.
Только если data.code действительно существует, то мы понимаем, что эту ошибку вернул наш бекенд и в errorCode мы записываем валидное значение.
А в противном случае мы понимаем, что случилась какая-то хрень и поэтому ставим unknown_error. Типа «случилась непонятная хрень». Так и напишем пользователю.

Задача программиста как можно меньше пугать юзеров непонятными ошибками и стараться по максимуму обрабатывать их руками.
Другое дело, что пользователям вообще по фигу, лежит ли у вас бекенд, упала ли база и кто-то накосячил в коде. Ему важно, что приложение не работает и все.
Поэтому есть смысл не перегружать юзера техническими деталями, а выдать общую ошибку и попросить попробовать позже. Естественно, предполагается, что мы уже устраняем косяки и поднимаем бекенд.

Это все, что касается валидации на сервере. Можно проверять, попытаться добавить существующий бренд или изменить урл /admin/api/v1/brands на какой-нибудь левый.
Если все сделали правильно, то при добавлении бренда будет уходить запрос на сервер, возвращаться ошибка и модалка не закроется, а выведет код.

Когда мы будем тестировать работу, то заметим одну визуальную неприятность. Допустим, попытались мы добавить пустой бренд, нам выскочила ошибка brand_empty.
Изменяем бренд в инпуте, а ошибка так и торчит. Бесит. Давайте добавим одну строчку, чтобы при установке фокуса в инпуте ошибка пропадала. Теперь инпут будет выглядеть так

    

@focus=»clearError» — вот и пригодился метод clearError!

Вывод нормального сообщения об ошибке вместо кода

Коды ошибок у нас есть, осталось задать им адекватные тексты. Можно сделать это и в компоненте BrandsNew, но мы поступим интереснее.
Заведем в папке admin/vue/src папку configs, а в ней файлик brands.js. Давно было пора, потому что хорошее дело — выносить подобные вещи в конфиги, а не держать их в компонентах.
В файлике-конфиге напишем так

    export default {
        errors: [{
            code: 'brand_empty',
            message: 'Бренд не может быть пустым'
        }, {
            code: 'brand_long_title',
            message: 'Название не должно превышать 20 символов'
        }, {
            code: 'brand_exists',
            message: 'Бренд с таким названием уже существует'
        }, {
            code: 'invalid_router',
            message: 'Ошибка запроса. Попробуйте еще раз'
        }, {
            code: 'unknown_error',
            message: 'Неизвестная ошибка. Попробуйте позже'
        }]
    }

Просто массив из двух полей: код ошибки и текст сообщения. Можно было обойтись объектом, чтобы не бегать по массиву. Но на практике такие простые конфиги часто разрастаются.
То появится тултип с подробным описанием ошибки, то для каждой ошибки нужен будет свой цвет или еще какая-то фигня. Или вместо текстового кода мы решим использовать числовой.
В общем, чтобы не приходилось переделывать структуру конфига, сразу будем использовать массив объектов — в него добавлять новые поля безопасней.

Идем обратно в компонент BrandsNew. В теге script импортируем lodash и конфиг брендов

    import _ from 'lodash';
    import config from '../configs/brands';

Создаем новое вычисляемое поле в computed

    errorMessage () {
        return this.errorCode !== ''
            ? _.find(config.errors, { code: this.errorCode }).message
            : '';
    }

Это и есть сообщение об ошибке, которое берется из конфига по нужному коду. Осталось воткнуть его в разметку вместо errorCode. Вот так

    {{errorMessage}}

Вот теперь все. У нас есть рабочая схема, по которой мы обрабатываем и клиентские, и серверные ошибки, используя один формат данных.
Теперь чтобы добавить новую проверку, достаточно будет воткнуть ее в метод validate, если проверка клиентская, и в php-шный код, если серверная.
Главное, вернуть с бекенда json формата { code: ‘код ошибки’, … }. И добавить новый объект в конфиг, чтобы показывать адекватные тексты пользователям.

До встречи в следующих уроках.

Все уроки админки на vue.js

Анонсы статей, обсуждения интернет-магазинов, vue, фронтенда, php, гита.

Истории из жизни айти и обсуждение кода.

I just use the catch. The same thing I was using before I switched to vuex. It’s probably the most universal and well documented solution and lets me continue to insert my errors into the html of the components like I was doing before. It also lets me continue to use my loading = true, loading = false html animation.

So I end up with 3 state properties, data, error, and loading. It seems to work for me. Your mileage may vary. I am also using vuex modules and namespacing but here is a simplified example without that

//somevuexstore.js

actions: {

fetchData(context) {

    axios
        .get("api/someendpoint")
        .then(response => {
            context.commit('loading')
            context.commit('organizations', response.data)

        }).catch(error => {
            console.log(error.response.data.message || error.message)
            context.commit('error', error)
        });
},

mutations: {
organizations(state, data) {
    return state.organization = data
},

error(state, data) {
    return state.error = data
},

loading(state) {
    return state.loading = false
},

state= {

organization: [],
error: '',
loading: true
}

Then in my component.vue it’s very similar to the way I was doing it before, just with the added computed properties.

computed: {
...mapState({
        getError: 'error',
        getLoading: 'loading',
        getAllOrg: 'organization',
}),
}

mounted() {
      this.$store.dispatch('fetchData')
}

And my html would be stuff like this.

<tr v-for="value in getAllOrg" :key="value.id">
   <td>{{ value.id }}</td>
   <td>{{ value.email }}</td>
   <td>{{ value.name }}</td>
   <td>{{ value.['created-at'] | formatDate }}</td>
</tr>

I insert the error messages where appropriate

<div v-if="getError" class="error">
   <p>{{ getError }}</p>
</div>

For loading animation I use vue spinners package inserted into html where appropriate.

<div v-if="getLoading" style="height:37px;">
    <p>
      <bar-loader class="custom-class" color="#c2c2c2" 
      getLoading="getLoading" 
      :width="130"></bar-loader>
   </p>


сентябрь
17
, 2019

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

— добавлять товары с пустыми названиями

— прописывать цены строками

— копипастить портянки текста в описания, хотя мы четко написали в инструкции — не больше 200 символов.

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

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

Валидация на клиенте и сервере. В чем разница?

Валидация на клиенте — это те ошибки, которые мы можем проверить средствами браузера. Например,

— юзер пытается добавить товар с пустым названием

— юзер забыл указать цену товара

— юзер вбивает длинное описание, хотя допускается максимум 200 символов.

Все это можно проверить в клиентской части приложения с помощью javascript.

Валидация на сервере — это все то, что можно проверить на клиенте, плюс некоторые специфичные вещи, часто связанные с базой. Например,

— юзер пытается добавить уже существующий товар

— юзер пытается удалить категорию, хотя в ней есть какие-то товары

— юзер пытается удалить бренд, но у него нет прав доступа на это

Все эти вещи проверяются на бекенде, клиент о них не знает.

Отмечу 2 момента:

— стоит по максимуму валидировать данные на клиенте

— дублировать эти же проверки на сервере.

Зачем это нужно?

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

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

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

Валидация в нашей админке интернет-магазина

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

В нашей админке мы не будем стелить соломку по всему коду, смысл уроков не в этом. Мы подробно рассмотрим валидацию только в одном компоненте, но покроем и клиентские, и серверные проверки.
И научимся показывать юзерам ошибки в человеческом виде.

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

— не пустой ли бренд

— не превышает ли бренд 20 символов (практического смысла мало, но для примера нормально)

— существует ли этот бренд в базе

— нет ли ошибки в запросе (неверный роутер, проходили в третьем уроке)

— общие ошибки, например, сервис недоступен, бекенд лежит

Первые 2 проверки будем делать на клиенте, следующие 2 — на сервере. Пятая — это все невошедшие ошибки, например, упавший ajax-запрос или 500-ка от сервера.

Дублировать клиентские проверки на сервере мы не будем. Они очень простые и легко реализуются на php, не будем перегружать код.
Другие серверные ошибки, номер 3 и 4, уже реализованы в третьем уроке, когда мы готовили api для админки.

Общая схема обработки ошибок

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

Чтобы понимать, какая именно ошибка произошла, добавим такое понятие — код ошибки. Например, на скриншоте код «brand_exists», что означает, бренд уже существует.
Это проверка серверная, но красное сообщение об этом не знает.
Код ошибки — единственное, на что будет ориентироваться это сообщение и все равно, откуда он придет, при клиентской валидации или с ответом от сервера.
Тексты сообщений мы будем брать из отдельного конфига. Теперь давайте сделаем это на практике.

Валидация на клиенте

Откроем компонент BrandsNew.vue и добавим новое поле errorCode, в раздел data. Это тот самый код, который определяет наличие ошибки. По умолчанию — пустая строка. То есть вот так

    data () {
        return {
            visible: false,
            newBrand: '',
            // Новое поле
            errorCode: ''
        }
    }

Добавим еще вычисляемое логическое поле isError, которое покажет, если ли в данный момент ошибка

    computed: {
        isError () {
            return this.errorCode !== '';
        }
    }

Дальше нам нужно вывести сообщение об ошибки в окне добавления бренда. Добавим такой код между инпутом-брендом и кнопкой Добавить

    Ошибка: {{errorCode}}

Это выделенный красным текст, пример взяли из библиотеки minicss. Напоминаю, наше приложение на этом css-фреймворке. Чуток поправим стили, добавим в style

    mark {
        display: block;
        margin: 5px 0 10px 5px;
    }

Но вернемся к разметке. v-if=»isError» означает, что сообщение будем выводить только, когда есть ошибка, логично. А в тексте сообщения выведем пока код ошибки — {{errorCode}}.
Сначала убедимся, что наша схема работает, а потом заменим бездушный errorCode нормальным текстом.

Итак, errorCode у нас есть, нужны методы для работы с ним. Один метод будет устанавливать код, другой очищать. Добавим в раздел methods

    setError (errorCode) {
        this.errorCode = errorCode;
    },
    clearError () {
        this.errorCode = '';
    }

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

Базовая подготовка сделана, пора заняться самой валидацией. Напомню, мы будем проверять, что новый бренд не пустой и что не длиннее 20 символов.
Метод валидации будет выполнять эти проверки и в случае ошибкок устанавливать соответствующий код codeError. Вот так выглядит метод validate

    validate (brand) {
        if (brand === '') {
            this.setError('brand_empty');
            return false;
        }
        if (brand.length > 20) {
            this.setError('brand_long_title');
            return false;
        }
    
        // Если валидация прошла успешно
        this.clearError();
        return true;
    }

Два простых if-а, которые проставляют нужные коды brand_empty или brand_long_title. А в случае успеха дергаем метод clearError, чтобы сбросить ошибку, если она была до этого.
Например, юзер ввел правильно инфу со второго раза.

Важный момент. Помимо установки codeError мы возвращаем true или false в зависимости от итога валидации.
Нам это нужно, чтобы понять, отправлять ли запрос на сервер с добавлением бренда или же ждать, пока юзер введет бренд правильно.

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

    addBrand () {
        this.$store.dispatch('brands/addBrand', this.newBrand);
        this.newBrand = '';
        this.closeModal();
    }

Все просто, вызываем действие через dispatch, очищаем инпут с названием и закрываем модалку. Этот код нужно чуть переделать, вызывать действие только если валидация прошла успешно.
Добавим условие if

    addBrand () {
        if (this.validate(this.newBrand)) {
            this.$store.dispatch('brands/addBrand', this.newBrand);
            this.newBrand = '';
            this.closeModal();
        }
    }

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

Валидация на сервере

С серверными проверками есть свои особенности.

Во-первых, на клиенте мы генерили коды ошибок сами, а для серверных нужно дождаться ответа и получить их из респонса.
Да, мы эти коды (brand_exists и invalid_router) сами сделали в уроке по API. Но вообще мы можем даже и не знать, что за коды нам возвращает бекенд.

Во-вторых, есть интерфейсный момент. Сейчас в методе addBrand сразу после dispatch мы очищаем поле бренда и закрываем модалку.
Это годится, если бренд успешно добавлен и никуда не годится, если сервер вернул ошибку.
Значит, мы должны не просто дернуть dispatch, а дождаться ответа от сервера и только потом решать, что делать.
Если ответ 200 и бренд добавлен, то закрывать модалку, а если ошибка, то выводить ее и оставлять модалку.

Если бы мы писали приложение на jquery, то сделали бы примерно так

    $.ajax({
        url: '',
        data: '',
        success: function() {
            // очищаем поле бренда и закрываем модалку
        },
        error: function() {
            // парсим код ошибки и выводим красное сообщение в модалке
        }
    });

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

Начнем с действия addBrand. Открываем файл store/modules/brands.js и смотрим, как реализовано сейчас

    addBrand (context, newBrand) {
        const data = 'title=' + newBrand;

        axios
            .post('/admin/api/v1/brands', data)
            .then(response => {
                context.commit('ADD_BRAND', response.data)
            });
    }

Отправляем запрос на сервер через axios и в случае успеха вызываем мутацию ADD_BRAND. Нам нужно сделать то же самое, только при этом еще и возвращать промис. Вот так

    addBrand (context, newBrand) {
        const data = 'title=' + newBrand;

        return new Promise((resolve, reject) => {
            axios
                .post('/admin/api/v1/brands', data)
                .then(response => {
                    context.commit('ADD_BRAND', response.data);
                    resolve(response);
                }, error => {
                    reject(error);
                });
        });
    }

Это стандартная заготовка для промисов. resolve и reject — это функции, которые выполняются соответственно при успехе и ошибке.
А что именно делают эти функции, будем решать уже в другом месте — опять в компоненте BrandsNew, в методе addBrand. Немного изменим код.

Было

    addBrand () {
        if (this.validate(this.newBrand)) {
            this.$store.dispatch('brands/addBrand', this.newBrand);
            this.newBrand = '';
            this.closeModal();
        }
    }

Стало

    addBrand () {
        if (this.validate(this.newBrand)) {
            this.$store.dispatch('brands/addBrand', this.newBrand).then(
                response => {
                    this.newBrand = '';
                    this.closeModal();
                },
                error => {
                    let resp = error.response;
                    let errorCode = (resp && resp.data && resp.data.code) ? resp.data.code : 'unknown_error';
                    this.setError(errorCode);
                });
        }
    }

Кода прибавилось, но немного. В метод resolve попадает

    response => {
        this.newBrand = '';
        this.closeModal();
    }

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

    error => {
        let resp = error.response;
        let errorCode = (resp && resp.data && resp.data.code) ? resp.data.code : 'unknown_error';
        this.setError(errorCode);
    });

Здесь параметр error уже нужен — это ответ от сервера, который нужно распарсить и вытащить код ошибки. Разберем по порядку. В первой строке пишем в переменную resp ответ от сервера.
Дальше идет диковатая конструкция resp && resp.data && resp.data.code. Зачем так сложно?
Ведь мы возвращаем ответ с бекенда в виде { code: ‘код ошибки’, … }, то есть поле code там будет всегда. Не совсем.
Код будет, но только для тех случаев, когда наш бекенд нормально обработает ошибку. Например, найдет, что бренд уже существует, или невалидный роутер (ошибка в запросе, в урле).
Если же бекенд просто лежит и вернет нам 500-ку, то в error.response может прийти null или пустая строка. Поэтому resp.data.code выкинет нам ошибку в консоль и поломает работу.

Поэтому именно здесь мы подстилаем соломку и проверяем всю цепочку error.response.data.code.
Только если data.code действительно существует, то мы понимаем, что эту ошибку вернул наш бекенд и в errorCode мы записываем валидное значение.
А в противном случае мы понимаем, что случилась какая-то хрень и поэтому ставим unknown_error. Типа «случилась непонятная хрень». Так и напишем пользователю.

Задача программиста как можно меньше пугать юзеров непонятными ошибками и стараться по максимуму обрабатывать их руками.
Другое дело, что пользователям вообще по фигу, лежит ли у вас бекенд, упала ли база и кто-то накосячил в коде. Ему важно, что приложение не работает и все.
Поэтому есть смысл не перегружать юзера техническими деталями, а выдать общую ошибку и попросить попробовать позже. Естественно, предполагается, что мы уже устраняем косяки и поднимаем бекенд.

Это все, что касается валидации на сервере. Можно проверять, попытаться добавить существующий бренд или изменить урл /admin/api/v1/brands на какой-нибудь левый.
Если все сделали правильно, то при добавлении бренда будет уходить запрос на сервер, возвращаться ошибка и модалка не закроется, а выведет код.

Когда мы будем тестировать работу, то заметим одну визуальную неприятность. Допустим, попытались мы добавить пустой бренд, нам выскочила ошибка brand_empty.
Изменяем бренд в инпуте, а ошибка так и торчит. Бесит. Давайте добавим одну строчку, чтобы при установке фокуса в инпуте ошибка пропадала. Теперь инпут будет выглядеть так

    

@focus=»clearError» — вот и пригодился метод clearError!

Вывод нормального сообщения об ошибке вместо кода

Коды ошибок у нас есть, осталось задать им адекватные тексты. Можно сделать это и в компоненте BrandsNew, но мы поступим интереснее.
Заведем в папке admin/vue/src папку configs, а в ней файлик brands.js. Давно было пора, потому что хорошее дело — выносить подобные вещи в конфиги, а не держать их в компонентах.
В файлике-конфиге напишем так

    export default {
        errors: [{
            code: 'brand_empty',
            message: 'Бренд не может быть пустым'
        }, {
            code: 'brand_long_title',
            message: 'Название не должно превышать 20 символов'
        }, {
            code: 'brand_exists',
            message: 'Бренд с таким названием уже существует'
        }, {
            code: 'invalid_router',
            message: 'Ошибка запроса. Попробуйте еще раз'
        }, {
            code: 'unknown_error',
            message: 'Неизвестная ошибка. Попробуйте позже'
        }]
    }

Просто массив из двух полей: код ошибки и текст сообщения. Можно было обойтись объектом, чтобы не бегать по массиву. Но на практике такие простые конфиги часто разрастаются.
То появится тултип с подробным описанием ошибки, то для каждой ошибки нужен будет свой цвет или еще какая-то фигня. Или вместо текстового кода мы решим использовать числовой.
В общем, чтобы не приходилось переделывать структуру конфига, сразу будем использовать массив объектов — в него добавлять новые поля безопасней.

Идем обратно в компонент BrandsNew. В теге script импортируем lodash и конфиг брендов

    import _ from 'lodash';
    import config from '../configs/brands';

Создаем новое вычисляемое поле в computed

    errorMessage () {
        return this.errorCode !== ''
            ? _.find(config.errors, { code: this.errorCode }).message
            : '';
    }

Это и есть сообщение об ошибке, которое берется из конфига по нужному коду. Осталось воткнуть его в разметку вместо errorCode. Вот так

    {{errorMessage}}

Вот теперь все. У нас есть рабочая схема, по которой мы обрабатываем и клиентские, и серверные ошибки, используя один формат данных.
Теперь чтобы добавить новую проверку, достаточно будет воткнуть ее в метод validate, если проверка клиентская, и в php-шный код, если серверная.
Главное, вернуть с бекенда json формата { code: ‘код ошибки’, … }. И добавить новый объект в конфиг, чтобы показывать адекватные тексты пользователям.

До встречи в следующих уроках.

Все уроки админки на vue.js

Анонсы статей, обсуждения интернет-магазинов, vue, фронтенда, php, гита.

Истории из жизни айти и обсуждение кода.

I just use the catch. The same thing I was using before I switched to vuex. It’s probably the most universal and well documented solution and lets me continue to insert my errors into the html of the components like I was doing before. It also lets me continue to use my loading = true, loading = false html animation.

So I end up with 3 state properties, data, error, and loading. It seems to work for me. Your mileage may vary. I am also using vuex modules and namespacing but here is a simplified example without that

//somevuexstore.js

actions: {

fetchData(context) {

    axios
        .get("api/someendpoint")
        .then(response => {
            context.commit('loading')
            context.commit('organizations', response.data)

        }).catch(error => {
            console.log(error.response.data.message || error.message)
            context.commit('error', error)
        });
},

mutations: {
organizations(state, data) {
    return state.organization = data
},

error(state, data) {
    return state.error = data
},

loading(state) {
    return state.loading = false
},

state= {

organization: [],
error: '',
loading: true
}

Then in my component.vue it’s very similar to the way I was doing it before, just with the added computed properties.

computed: {
...mapState({
        getError: 'error',
        getLoading: 'loading',
        getAllOrg: 'organization',
}),
}

mounted() {
      this.$store.dispatch('fetchData')
}

And my html would be stuff like this.

<tr v-for="value in getAllOrg" :key="value.id">
   <td>{{ value.id }}</td>
   <td>{{ value.email }}</td>
   <td>{{ value.name }}</td>
   <td>{{ value.['created-at'] | formatDate }}</td>
</tr>

I insert the error messages where appropriate

<div v-if="getError" class="error">
   <p>{{ getError }}</p>
</div>

For loading animation I use vue spinners package inserted into html where appropriate.

<div v-if="getLoading" style="height:37px;">
    <p>
      <bar-loader class="custom-class" color="#c2c2c2" 
      getLoading="getLoading" 
      :width="130"></bar-loader>
   </p>

Наличие  в приложении эффективного механизма обработки ошибок дает много преимуществ:

  • Предотвращение остановки приложения при возникновении необработанного исключения.
  • Возможность отслеживания и оперативного исправления ошибок.
  • Информирование пользователей о произошедшем сбое и предоставление альтернативного варианта интерфейса.

Это четвертая часть серии статей о разработке приложения на Vue.js. Исходники созданного приложения доступны здесь.

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

  • С помощью глобальной конфигурации Vue.js.
  • С помощью хуков ErrorBoundaries или errorCaptured .
  • Применение глобальной конфигурации в Vue.js:
  • Использование хуков errorBoundaries и errorCaptured:
  • Заключение

В фреймворке Vue.js есть объект Vue.config. Он содержит глобальные конфигурации приложения: журналов ошибок и предупрежденийсредства разработкиобработчика ошибок  и т.д.

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

Пример регистрации обработчика ошибок:

import Vue from 'vue';

Vue.config.errorHandler = (err, vm, info) => {
  

};

Обработчик принимает три параметра:

  1. err: трассировка ошибок, содержит message и error stack;
  2. vm: компонент Vue/экземпляр, в котором произошла ошибка;
  3. info: специфическая информация Vue: хуки жизненного цикла, события и т.д.

Обработчик Vue.config.errorHandler фиксирует ошибки, характерные для экземпляров Vue. Он не сможет зафиксировать ошибки, которые произошли вне экземпляра.

Для перехвата сторонних ошибок, которые возникают вне экземпляра Vue, можно обработать событие окна onerror. Для этого нужно зарегистрировать хэндлер, который будет перехватывать все необработанные исключения вне экземпляра Vue. Например:

window.onerror = function(message, source, lineno, colno, error) {
  //код для обработки ошибок
};

В Vue.js 2.5.0 появился новый хук errorCaptured. Он позволяет обрабатывать специфические ошибки компонента внутри самого компонента. Хук errorCaptured имеет такой же синтаксис и принимает те же параметры, что и errorHandler:

	export default {
	  name: "app-user-list",
	
	  created() {
	    this.$store.dispatch(actionsTypes.FETCH_USER_DATA);
	  },
	
	  errorCaptured(err, vm, info) {
	    // err: трассировка ошибки
	    // vm: компонент, в котором произошла ошибка
	    // info: специфическая информация об ошибке.
	    // TODO: Perform any custom logic or log to server
	    // возвращаем false, чтобы остановить распространение ошибок. Затем переходим к родительскому или глобальному обработчику ошибок.
	  }
	};

Хук errorCaptured в компоненте Vue

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

error-boundary.vue

	<template>
	  <div>
	    <slot
	      v-if="err"
	      name="error"
	      v-bind:err="err"
	      v-bind:vm="vm"
	      v-bind:info="info"
	    >Something went wrong</slot>
	    <slot v-else></slot>
	  </div>
	</template>
	
	<script>
	export default {
	  name: "error-boundary",
	  props: {
	    stopPropagation: Boolean
	  },
	  data() {
	    return {
	      err: false,
	      vm: null,
	      info: null
	    };
	  },
	  errorCaptured(err, vm, info) {
	    this.err = err;
	    this.vm = vm;
	    this.info = info;
	
	    return !this.stopPropagation;
	  }
	};
	</script>
	
	<style lang="scss" scoped>
	</style>

Пример использования:

<template>
	  <div class="user-list">
	    <error-boundary>
	      <app-user-item/>
	    </error-boundary>
	  </div>
	</template>

Любая не перехваченная ошибка или исключение в компоненте app-user-item будет обработана компонентом error-boundary.

Если приложение работает в пределах error-boundary , тогда оно будет вести себя как глобальный обработчик ошибок errorHandler.

По умолчанию ошибка, перехваченная хуками errorCaptured , будет распространяться на его родительский компонент и затем на глобальный обработчик ошибок (config.errorHandler). Такое  распространение можно остановить, вернув false из метода errorCaptured.

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

В этой статье мы рассмотрели реализацию глобальной обработки ошибок с помощью объекта config.errorHandler. А также локальный обработчик на основе хука errorCaptured.

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

Ошибки!

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

<div id="app" v-cloak>
  Hello, {{name}}
</div>

Этот пример не будет отображать ошибку для пользователя, он должен выводить предупреждение [Vue warn] в консоль.

Error messages

Вы можете просмотреть этот пример здесь:

Для второго примера я попытался связать переменную с вычисляемым свойством, которая выводит ошибку:

<div id="app" v-cloak>
  Hello, {{name2}}
</div>

<script>
const app = new Vue({
  el:'#app',
  computed:{
    name2() {
      return x;
    }
  }
})
</script>

Этот код так же выдает [Vue warn] и обычную ошибку в консоли но при этом ничего не показывает пользователю в окне браузера.

Error messages

Вот код для него.

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

<div id="app" v-cloak>
 <button @click="doIt">Do It</button>
</div>

<script>
const app = new Vue({
  el:'#app',
  methods:{
    doIt() {
      return x;
    }
  }
})
</script>

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

Error with the click handler

И вот код для него:

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

Итак, как вы обрабатываете ошибки в приложениях Vue? Я должен сказать, что был немного удивлен, что в главном Vue Guide не было четко определенного раздела по обработке ошибок.

Results for Error

Да, есть общее описание в руководстве, но текст достаточно короткий, и весь может поместиться в одну цитату:

Если во время рендеринга компонента возникает ошибка выполнения, она будет передана в глобальную конфигурационную функцию Vue.config.errorHandler(если она была задействована). Возможно, было бы неплохо использовать этот хук вместе с сервисом отслеживания ошибок, таким как Sentry, который обеспечивает официальную интеграцию для Vue.

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

  • errorHandler
  • warnHandler
  • renderError
  • errorCaptured
  • window.onerror (не Vue-специфическая техника)

Первый метод обработки ошибок: errorHandler

Первый метод, который мы рассмотрим, это errorHandler. Как вы можете догадаться, это общий обработчик ошибок для приложений Vue.js. Его использование выглядит так:

Vue.config.errorHandler = function(err, vm, info) {

}

В приведенном выше объявлении переменная err — это фактический объект ошибки, info — это строка ошибки, специфичная для Vue, а vm — фактическое приложение Vue. Помните, что одновременно на одной веб-странице может работать несколько приложений Vue. Этот обработчик ошибок будет применяться ко всем из них. Рассмотрим этот простой пример:

Vue.config.errorHandler = function(err, vm, info) {
  console.log(`Error: ${err.toString()}nInfo: ${info}`);
}

Для первого примера кода эта обработка ошибки ничего не делает. Если вы помните, оно выдает предупреждение, а не ошибку.

Для второго примера кода, она обработает ошибку и выдаст сообщение:

Error: ReferenceError: x is not defined
Info: render

Наконец, для третьего примера она выдаст такой сообщение:

Error: ReferenceError: x is not defined
Info: v-on handler

Теперь давайте проверим следующий метод.

Второй метод обработки ошибок: warnHandler

warnHandler Vue предупреждения. Обратите внимание, что этот обработчик игнорируется во время режима продакт. Обработчик метода также немного отличается:

Vue.config.warnHandler = function(msg, vm, trace) {

}

Переменные msg, и vm самоочевидны, а trace содержит дерево компонентов. Рассмотрим этот пример:

Vue.config.warnHandler = function(msg, vm, trace) {
  console.log(`Warn: ${msg}nTrace: ${trace}`);
}

Первый пример кода теперь обрабатывается и выдает предупреждения:

Warn: Property or method 'name' is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
Trace: 

(found in <Root>)

Второй и третий примеры не меняются. Вы можете просмотреть работу примером ниже:

Третий метод обработки ошибок: renderError

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

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

const app = new Vue({
  el:'#app',
  renderError (h, err) {
    return h('pre', { style: { color: 'red' }}, err.stack)
  }
})

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

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

Четвертый метод обработки ошибок: errorCaptured

Финальный (специфичной для Vue) метод errorCaptured. В документации о нем сказано:

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

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

Чтобы проверить этот метод, я создал родительский / дочерний набор компонентов, например:

Vue.component('cat', {
  template:`
<div><h1>Cat: </h1>
  <slot></slot>
</div>`,
  props:{
    name:{
      required:true,
      type:String
    }
  },
   errorCaptured(err,vm,info) {
    console.log(`cat EC: ${err.toString()}ninfo: ${info}`); 
     return false;
  }

});

Vue.component('kitten', {
  template:'<div><h1>Kitten: </h1></div>',
  props:{
    name:{
      required:true,
      type:String
    }
  }
});

Обратите внимание, что в компоненте kitten есть ошибка. Теперь, если я попытаюсь использовать:

<div id="app" v-cloak>
  <cat name="my cat">
      <kitten></kitten>
  </cat>
</div>

Я получу сообщение от обработчика:

cat EC: TypeError: dontexist is not a function
info: render

Вы можете просмотреть это в коде ниже.

Так что да … интересный метод. Я предполагаю, что он будет в основном использоваться людьми, создающими библиотеки компонентов с отношениями типа родительский / дочерний. Это скорее метод «разработчика библиотеки», чем метод «обычного разработчика». Но опять же — это только мое первоначальное впечатление от этого метода.

Единый метод, чтобы управлять ими всеми: window.onerror

Obligatory LOTR reference ring

Последний (и самый мощный) вариант — использование window.onerror, глобальный обработчик ошибок для всего, что может пойти не так с вашим JavaScript. Обработчик имеет форму:

window.onerror = function(message, source, line, column, error) {

}

Вероятно, единственное,о чем вы не можете догадаться, это что такое переменная source. В ней содержится URL скрипта. Здесь все становится интереснее. Если вы определите этот метод и не используете Vue.config.errorHandler, то он не будет работать. Vue ожидает, что вы определите errorHandler а, если вы этого не сделаете, не распространит ошибку за пределы себя.

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

Заключение

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

Перевод статьи: Handling Errors in Vue.js

Была ли вам полезна эта статья?

In software development, things are not as smooth as they may seem to the user. There are always errors and bugs that developers must deal with behind the scenes. As you might imagine, debugging and error tracing can be time-consuming, and the tools you use can make a huge difference in productivity.

If you’re using Vue.js, you’ve surely encountered various types of errors, and there’s usually more than one way to handle them. In this tutorial, we’ll review some best practices and demonstrate how to handle errors, set up a debugger, and trace errors efficiently in Vue.js.

We’ll use Vue CLI to set up our demo project, a to-do app that displays a list of items from the Placeholder API.

The full code for this project is available on GitHub.

What is error handling?

Error handling refers to the process of tracing, handling, and resolving application errors without negatively affecting the user experience. It aims to accomplish three objectives:

  1. Prevent the application from breaking unexpectedly in production when there’s an unhandled exception
  2. Discover what happened when an error is logged
  3. Improve the user experience (e.g., by displaying messages when bugs are discovered and fixed)

What is debugging?

Debugging is the process of identifying and removing errors and resolving bugs in software. It involves analyzing a problem, identifying its origin, determining the cause, and charting a course to resolve it as efficiently as possible.

Debugging in Vue.js

Vue.js provides a debugger that helps you handle errors and quickly address bugs in your application. VueJs Debugger is well-integrated with VSCode and both Chrome and Firefox.

First, download and install the Vue Debugger extension for Chrome or Firefox. You can read more on how to configure it in the Vue.js docs.

Once you’ve installed the debugger, go to VSCode Activity Bar and click the debugging icon. You’ll see a gear/settings icon. Click it and select your preferred browser.

Next, open the launch.json file and paste in the corresponding configuration based on the browser you selected.

Once the setup is complete, tracing errors is as easy as setting up several breakpoints in your application and running the debugger you just installed.

What is tracing?

Tracing is the process of searching for the origin of an error and identifying the cause in a stack trace.

As you might’ve guessed, Vue Debugger also makes tracing a lot easier.

Setting up a breakpoint

Before you can achieve effective tracing, you need to learn how to set up a breakpoint.

Double-click on the line number in your VSCode to add a breakpoint. Then, run npm run serve to serve your project. Go to the debug view in your editor and select your preferred browser configuration. Press 5 or click the green play button. At this point, your breakpoints should hit when the code executes.

If you look at the debug view, you can spot some useful data for debugging your application.

Debugging Data in VS Code

Handling errors in Vue.js

Handling errors in a Vue.js application can be complicated. The best approach is to break it into steps.

In any application I develop, I always formulate a plan for handling errors, usually by creating a generic ErrorService class and including functions to handle and process the errors coming in according to their type.

Types of errors

There are four main types of errors you’re likely to encounter in your Vue.js app:

  1. Syntax errors occur when you use the wrong syntax
  2. Runtime errors are caused by illegal operations during execution
  3. Logical errors are difficult to identify because they result from mistakes in the program logic
  4. HTTP errors are common when you’re working with APIs

Now let’s zoom in on some specific examples of error handling and debugging activities you can perform with Vue Debugger.

Create an error service in Vue.js

The ErrorService class will handle all your errors and decide how to process them.

import Swal from "sweetalert2";
import "sweetalert2/dist/sweetalert2.min.css";

export default class ErrorService {
  constructor() {
    // this.initHandler();
  }

  static onError(error) {
    const response = error.response;
    if (response && response.status >= 400 && response.status < 405) {
      // You can handle this differently
      ErrorService.sentryLogEngine(error);
      return false;
    }
    // Send Error to Log Engine e.g LogRocket
    ErrorService.logRocketLogEngine(error);
  }

  static onWarn(error) {
    // Send Error to Log Engine e.g LogRocket
    this.logRocketLogEngine(error);
  }

  static onInfo(error) {
    // You can handle this differently
    this.sentryLogEngine(error);
  }

  static onDebug(error) {
    const response = error.response;
    if (response && response.status >= 400 && response.status < 405) {
      // You can handle this differently
      this.sentryLogEngine(error);
      return false;
    }
    // Send Error to Log Engine e.g LogRocket
    this.logRocketLogEngine(error);
  }

  static initHandler() {
    const scope = this;
    window.onerror = (message, url, lineNo, columnNo, error) => {
      console.log(error, "test");
      if (error) {
        scope.onError(error);
        console.log(message, url, lineNo, columnNo, error);
      }
    };
  }

  static displayErrorAlert(message) {
    Swal.fire({
      title: "Error!",
      text: message,
      icon: "error",
    });
  }

  static logRocketLogEngine(error) {
    // Implement LogRocket Engine here
    console.log(error, "LogRocket");
  }

  static sentryLogEngine(error) {
    // Implement Sentry Engine here
    console.log(error, "Sentry");
  }
}

Catch all possible Vue errors

Vue.js offers an error handler to catch all possible Vue errors. You can do this inside the main.js file.

import Vue from "vue";
import App from "./App.vue";
import { ErrorService } from "./Services/ErrorService";
import store from "./store";

Vue.config.productionTip = false;

// Handle all Vue errors
Vue.config.errorHandler = (error) => ErrorService.onError(error);

new Vue({
  store,
  render: (h) => h(App),
}).$mount("#app");

ErrorService in Vuex

Using the ErrorService class in Vuex is a great way to handle and process HTTP errors from Axios. You can also store the errors in Vuex State so they can be displayed to the user gracefully.

import Vue from "vue";
import Vuex from "vuex";
import { ErrorService } from "./Services/ErrorService";
import axios from "axios";

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    todos: [],
    errors: [],
    users: [],
  },

  actions: {
    async getTodos({ commit }) {
      try {
        const response = await axios.get(
          `https://jsonplaceholder.typicode.com/todos`
        );
        const { data } = response;
        commit("STORE_TODOS", data);
      } catch (error) {
        // Handling HTTPs Errors
        commit("STORE_ERRORS", error);
      }
    },

    async getUsers({ commit }) {
      try {
        const response = await axios.get(
          `https://jsonplaceholder.typicode.com/users`
        );
        const { data } = response;
        commit("STORE_USERS", data);
      } catch (error) {
        // Handling HTTPs Errors
        commit("STORE_ERRORS", error);
      }
    },
  },

  mutations: {
    STORE_TODOS: (state, data) => {
      state.todos = data;
    },

    STORE_ERRORS: (state, error) => {
      // Call Error Service here
      ErrorService.onError(error);
      ErrorService.initHandler();

      // Store error to state(optional)
      if (error.response) {
        state.errors = error.response;
      }
    },

    STORE_USERS: (state, data) => {
      state.users = data;
    },
  },

  getters: {
    getTodo: (state) => (id) => {
      return state.todos.find((todo) => todo.id == id);
    },
    getUser: (state) => (id) => {
      return state.users.find((user) => user.id == id);
    },
  },

  // strict: true
});

export default store;

Displaying errors in a component

Since the errors are saved in the Vuex State, which enables us to leverage the reactivity of Vue, we can display them on the error component like so:

<script>
import { mapGetters } from "vuex";
import ErrorService from "../Services/ErrorService";

export default {
  name: "HelloWorld",
  props: {
    todo: Object,
  },
  computed: {
    ...mapGetters(["getUser"]),
  },

  methods: {
    getUserName(id) {
      const user = this.getUser(id);
      if (user) return user.username;
    },

    // Handling Errors in component
    methodThrowsException() {
      try {
        // Do unexpected job
      } catch (error) {
        ErrorService.onError(error);
      }
    },
  },
};
</script>

Displaying errors with plugins in Vue.js

You can display these errors to the user in different ways. We’ll use the vue-sweetalert2 plugin to display our errors.

<script>
import { mapGetters } from "vuex";
import ErrorService from "../Services/ErrorService";

export default {
  name: "HelloWorld",
  props: {
    todo: Object,
  },
  computed: {
    ...mapGetters(["getUser"]),
  },

  methods: {
    getUserName(id) {
      const user = this.getUser(id);
      if (user) return user.username;
    },

    // Display Error with SweetAlert (when Name is Click)
    displayAlert() {
      ErrorService.displayErrorAlert("Testing message");
    },
  },
};
</script>

Conclusion

The ability to handle errors and resolve bugs in your application efficiently is crucial. If you want to maximize your productivity, choosing the right tools and methods for debugging your Vue.js app can make a huge difference.

Hopefully, this walkthrough gave you a solid, foundational understanding of error management, debugging, and tracing in VueJS.

Experience your Vue apps exactly how a user does

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket. LogRocket Dashboard Free Trial Bannerhttps://logrocket.com/signup/

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps — Start monitoring for free.

Логотип Vue
Vue.js

  • Обучение

    • Документация

      • Руководство
      • API
      • Рекомендации
      • Примеры
      • Книга рецептов
    • Видео курсы

      • Vue Mastery

      • Vue School

  • Экосистема

    • Помощь

      • Форум
      • Чат
      • Митапы
    • Инструментарий

      • Инструменты разработчика
      • Vue CLI
      • Vue Loader
    • Официальные плагины

      • Vue Router
      • Vuex
      • Vue Server Renderer
    • Новости

      • Еженедельные новости
      • Roadmap
      • События
      • Twitter
      • Блог
      • Вакансии
      • Сообщество разработчиков
  • Команда
  • Ресурсы

    • Партнёры
    • Темы
    • Awesome Vue
    • Найти пакеты для Vue
  • Поддержать Vue

    • Единоразовые пожертвования
    • Повторяющиеся взносы
    • Магазин футболок
  • Переводы

    • English
    • 中文
    • 日本語
    • 한국어
    • Português
    • Français
    • Tiếng Việt
    • Español
    • Bahasa Indonesia

Эта документация для версий v2.x и ранее.
Для v3.x, документация на русском здесь.

Используем axios для доступа к API

Простой пример

Неоднократно при создании веб-приложения вам может понадобиться получать и отображать данные из API. Существует несколько способов сделать это, но наиболее популярным решением является использование axios, основанного на Promise HTTP-клиента.

В этом упражнении мы будем использовать CoinDesk API для отображения цен на Биткойн, обновляемых каждую минуту. Прежде всего, подключим axios с помощью npm, yarn или ссылки на CDN.

Существует множество вариантов, как мы можем запрашивать информацию из API, но прежде необходимо узнать, в каком виде предоставляются данные, чтобы понимать, как их отображать. Для этого сделаем запрос к конечной точке (endpoint) API и выведем результат. Как можно убедиться из документации API CoinDesk, для получения данных мы будем делать запрос на https://api.coindesk.com/v1/bpi/currentprice.json. Изначально необходимо создать свойство в data для хранения нашей информации, далее извлечём и сохраним данные, используя хук жизненного цикла mounted.

new Vue({
el: '#app',
data() {
return {
info: null
};
},
mounted() {
axios
.get('https://api.coindesk.com/v1/bpi/currentprice.json')
.then(response => (this.info = response));
}
});
<div id="app">
{{ info }}
</div>

И вот что мы получаем:

Посмотрите Pen First Step Axios and Vue by Vue (@Vue) на CodePen.

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

Пример из жизни: работа с данными

Отображение данных из API

Типичная ситуация, когда необходимая информация находится внутри ответа сервера и нам необходимо знать структуру данных для получения доступа. В нашем случае, нужная информация о цене находится в response.data.bpi. Если мы используем это, то запрос будет выглядеть следующим образом.

axios
.get('https://api.coindesk.com/v1/bpi/currentprice.json')
.then(response => (this.info = response.data.bpi));

Посмотрите Pen Second Step Axios and Vue by Vue (@Vue) на CodePen.

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

<div id="app">
<h1>Bitcoin Price Index</h1>
<div
v-for="currency in info"
class="currency"
>
{{ currency.description }}:
<span class="lighten">
<span v-html="currency.symbol"></span>{{ currency.rate_float | currencydecimal }}
</span>
</div>
</div>
filters: {
currencydecimal (value) {
return value.toFixed(2)
}
}

Посмотрите Pen Third Step Axios and Vue by Vue (@Vue) на CodePen.

Обработка ошибок

Бывают моменты, когда мы не получили необходимые данные из API. Может быть множество причин, из-за которых вызов axios мог закончиться неудачей, например:

  • API не был доступен.
  • Запрос был сделан неправильно.
  • API не предоставил данные в ожидаемом нами формате.

При выполнении этого запроса мы должны проверять такие обстоятельства и предоставлять информацию в каждом случае, чтобы мы знали, как справиться с этой проблемой. В вызове axios мы сделаем это, используя catch.

axios
.get('https://api.coindesk.com/v1/bpi/currentprice.json')
.then(response => (this.info = response.data.bpi))
.catch(error => console.log(error));

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

new Vue({
el: '#app',
data() {
return {
info: null,
loading: true,
errored: false
};
},
filters: {
currencydecimal(value) {
return value.toFixed(2);
}
},
mounted() {
axios
.get('https://api.coindesk.com/v1/bpi/currentprice.json')
.then(response => {
this.info = response.data.bpi;
})
.catch(error => {
console.log(error);
this.errored = true;
})
.finally(() => (this.loading = false));
}
});
<div id="app">
<h1>Bitcoin Price Index</h1>

<section v-if="errored">
<p>We're sorry, we're not able to retrieve this information at the moment, please try back later</p>
</section>

<section v-else>
<div v-if="loading">Loading...</div>

<div
v-else
v-for="currency in info"
class="currency"
>
{{ currency.description }}:
<span class="lighten">
<span v-html="currency.symbol"></span>{{ currency.rate_float | currencydecimal }}
</span>
</div>

</section>
</div>

Вы можете нажимать на кнопку перезапуска (находится в правом нижнем углу во вкладке Result), чтобы увидеть индикатор загрузки во время получения данных из API.

Посмотрите Pen Fourth Step Axios and Vue by Vue (@Vue) на CodePen.

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

Альтернативы

Fetch API

Fetch API — мощный нативный API для создания запросов. Может вы слышали, что одно из преимуществ Fetch API в том, что не нужно загружать внешние зависимости для его использования, что является правдой! Однако… он ещё не полностью поддерживается браузерами, поэтому всё равно необходимо использовать полифил. Есть подводные камни при работе с его API, поэтому многие сейчас предпочитают axios. В будущем это может измениться.

Если вы заинтересовались Fetch API — существуют очень хорошие статьи, где объясняются тонкости его использования.

Итоги

Существует множество способов работы с Vue и axios, выходящие за рамки получения и отображения данных из API. Вы можете также взаимодействовать с бессерверными функциями (Serverless Functions), публикацией/редактированием/удалением через API, к которому вы имеете доступ, и т.д. Простая интеграция этих двух библиотек сделала axios очень распространённым выбором среди разработчиков, которым необходимо интегрировать HTTP-клиенты в их приложения.

  • Vue cli ошибка при установке
  • Vue axios обработка ошибок
  • Vts ошибка а50 motsup
  • Vts optima коды ошибок
  • Vtosters не работает музыка ошибка