React axios обработка ошибок

I have a React component that calls a function getAllPeople:

componentDidMount() {
   getAllPeople().then(response => {
      this.setState(() => ({ people: response.data }));
    });
  } 

getAllPeople is in my api module:

export function getAllPeople() {
  return axios
    .get("/api/getAllPeople")
    .then(response => {
      return response.data;
    })
    .catch(error => {
      return error;
    });
}

I think this is a very basic question, but assuming I want to handle the error in my root component (in my componentDidMount method), not in the api function, how does this root component know whether or not I the axios call returns an error? I.e. what is the best way to handle errors coming from an axios promise?

asked Oct 29, 2017 at 21:32

GluePear's user avatar

GluePearGluePear

7,14620 gold badges67 silver badges118 bronze badges

Better way to handle API error with Promise catch method*.

axios.get(people)
    .then((response) => {
        // Success
    })
    .catch((error) => {
        // Error
        if (error.response) {
            // The request was made and the server responded with a status code
            // that falls out of the range of 2xx
            // console.log(error.response.data);
            // console.log(error.response.status);
            // console.log(error.response.headers);
        } else if (error.request) {
            // The request was made but no response was received
            // `error.request` is an instance of XMLHttpRequest in the 
            // browser and an instance of
            // http.ClientRequest in node.js
            console.log(error.request);
        } else {
            // Something happened in setting up the request that triggered an Error
            console.log('Error', error.message);
        }
        console.log(error.config);
    });

answered May 31, 2018 at 3:39

Sagar's user avatar

SagarSagar

4,4053 gold badges32 silver badges37 bronze badges

0

The getAllPeople function already returns the data or error message from your axios call. So, in componentDidMount, you need to check the return value of your call to getAllPeople to decide whether it was an error or valid data that was returned.

componentDidMount() {
   getAllPeople().then(response => {
      if(response!=error) //error is the error object you can get from the axios call
         this.setState(() => ({ people: response}));
      else { // your error handling goes here
       }
    });
  } 

If you want to return a promise from your api, you should not resolve the promise returned by your axios call in the api. Instead you can do the following:

export function getAllPeople() {
  return axios.get("/api/getAllPeople");
}

Then you can resolve in componentDidMount.

componentDidMount() {
   getAllPeople()
   .then(response => {          
         this.setState(() => ({ people: response.data}));
     })
   .catch(error => { 
         // your error handling goes here
     }
  } 

Yana Trifonova's user avatar

answered Oct 29, 2017 at 21:47

palsrealm's user avatar

palsrealmpalsrealm

5,01320 silver badges25 bronze badges

2

My suggestion is to use a cutting-edge feature of React. Error Boundaries

This is an example of using this feature by Dan Abramov.
In this case, you can wrap your component with this Error Boundary component.
What is special for catching the error in axios is that you can use
interceptors for catching API errors.
Your Error Boundary component might look like

import React, { Component } from 'react';

const errorHandler = (WrappedComponent, axios) => {
  return class EH extends Component {
    state = {
      error: null
    };

    componentDidMount() {
      // Set axios interceptors
      this.requestInterceptor = axios.interceptors.request.use(req => {
        this.setState({ error: null });
        return req;
      });

      this.responseInterceptor = axios.interceptors.response.use(
        res => res,
        error => {
          alert('Error happened');
          this.setState({ error });
        }
      );
    }

    componentWillUnmount() {
      // Remove handlers, so Garbage Collector will get rid of if WrappedComponent will be removed 
      axios.interceptors.request.eject(this.requestInterceptor);
      axios.interceptors.response.eject(this.responseInterceptor);
    }

    render() {
      let renderSection = this.state.error ? <div>Error</div> : <WrappedComponent {...this.props} />
      return renderSection;
    }
  };
};

export default errorHandler;

Then, you can wrap your root component passing axios instance with it

errorHandler(Checkout, api)

As a result, you don’t need to think about error inside your component at all.

answered May 7, 2018 at 18:51

Yevhenii Herasymchuk's user avatar

4

You could check the response before setting it to state. Something like

componentDidMount() {
   getAllPeople().then(response => {
       // check if its actual response or error
        if(error) this.setState(() => ({ error: response }));
        else this.setState(() => ({ people: response}));
    });
  }

Its relying on the fact that axios will return different objects for success and failures.

answered Oct 29, 2017 at 21:40

Hozefa's user avatar

HozefaHozefa

1,6666 gold badges22 silver badges34 bronze badges

The solution from Yevhenii Herasymchuk was very close to what I needed however, I aimed for an implementation with functional components so that I could use Hooks and Redux.

First I created a wrapper:

export const http = Axios.create({
    baseURL: "/api",
    timeout: 30000,
});

function ErrorHandler(props) {

useEffect(() => {
    //Request interceptor
    http.interceptors.request.use(function (request) {
        // Do something here with Hooks or something else
        return request;
    });

    //Response interceptor
    http.interceptors.response.use(function (response) {
        if (response.status === 400) {
            // Do something here with Hooks or something else
            return response;
        }
        return response;
    });
}, []);

return props.children;
}

export default ErrorHandler;

Then I wrapped the part of the project that I needed to check how axios behaved.

<ErrorHandler>
  <MainPage/>
</ErrorHandler>

Lastly, I import the axios instance(http) wherever I need it in the project.

Hope it helps anyone that wishes for a different approach.

answered Sep 8, 2021 at 13:57

GeorgeCodeHub's user avatar

1

I just combined both answers by Yevhenii Herasymchuk and GeorgeCodeHub, fixed some mistakes and ported it into React hooks. So here is my final working version:

// [project-directory]/src/lib/axios.js

import Axios from 'axios';

const axios = Axios.create({
  baseURL: process.env.NEXT_PUBLIC_BACKEND_URL,
  headers: {
    'X-Requested-With': 'XMLHttpRequest',
  },
  withCredentials: true,
});

export default axios;


// [project-directory]/src/components/AxiosErrorHandler.js

import {useEffect} from 'react';
import axios from '@/lib/axios';

const AxiosErrorHandler = ({children}) => {
  useEffect(() => {
    // Request interceptor
    const requestInterceptor = axios.interceptors.request.use((request) => {
      // Do something here with request if you need to
      return request;
    });

    // Response interceptor
    const responseInterceptor = axios.interceptors.response.use((response) => {
      // Handle response here

      return response;
    }, (error) => {
      // Handle errors here
      if (error.response?.status) {
        switch (error.response.status) {
          case 401:
            // Handle Unauthenticated here
            break;
          case 403:
            // Handle Unauthorized here
            break;
          // ... And so on
        }
      }

      return error;
    });

    return () => {
      // Remove handlers here
      axios.interceptors.request.eject(requestInterceptor);
      axios.interceptors.response.eject(responseInterceptor);
    };
  }, []);

  return children;
};

export default AxiosErrorHandler;

Usage:

// Wrap it around your Layout or any component that you want

return (
  <AxiosErrorHandler>
    <div>Hello from my layout</div>
  </AxiosErrorHandler>
);

answered Feb 1 at 15:59

Hooman's user avatar

HoomanHooman

1,0632 gold badges18 silver badges42 bronze badges

1

There is 2 options to handle error with axios in reactjs

  1. Using catch method:

    axios.get(apiUrl).then(()=>{
    //respons
    }).catch((error)=>{
    //handle error here
    })

  2. try catch

    try {
    const res = await axios.get(apiUrl);
    } catch(err){
    //handle error here…
    }

answered Mar 13 at 9:29

Khurshid Ansari's user avatar

Khurshid AnsariKhurshid Ansari

4,5332 gold badges31 silver badges50 bronze badges

axios.get('/user/12345')
  .catch(function (error) {
    if (error.response) {
      // The request was made and the server responded with a status code
      // that falls out of the range of 2xx
      console.log(error.response.data);
      console.log(error.response.status);
      console.log(error.response.headers);
    } else if (error.request) {
      // The request was made but no response was received
      // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
      // http.ClientRequest in node.js
      console.log(error.request);
    } else {
      // Something happened in setting up the request that triggered an Error
      console.log('Error', error.message);
    }
    console.log(error.config);
  });

Using the validateStatus config option, you can define HTTP code(s) that should throw an error.

axios.get('/user/12345', {
  validateStatus: function (status) {
    return status < 500; // Resolve only if the status code is less than 500
  }
})

Using toJSON you get an object with more information about the HTTP error.

axios.get('/user/12345')
  .catch(function (error) {
    console.log(error.toJSON());
  });

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

В этой статье мы рассмотрим Axios, стороннюю библиотеку HTTP-клиента на основе обещаний для node.js и браузера. Мы рассмотрим, как использовать Axios для выполнения HTTP-запросов к API. Мы также рассмотрим, как обрабатывать ошибки при выполнении HTTP-запросов.

Axios

Axios — это сторонняя клиентская библиотека HTTP на основе promise для node.js и браузер. Он изоморфен (= он может запускаться в браузере и nodejs с одной и той же кодовой базой). На стороне сервера он использует собственный node.js http-модуль, в то время как клиент (браузер) использует XMLHttpRequests.

Вы можете добавить в свой проект либо через сеть распространения контента, либо через CDN, либо установить его с помощью менеджера пакетов, такого как npm или yarn. Аксиомы могут запускаться в браузере или node.js окружающая среда.

Это очень популярная библиотека для выполнения HTTP-запросов. Он очень прост в использовании и обладает множеством функций.

Он также очень популярен в сообществе React. Он используется во многих проектах React. Лично мы приведем несколько существенных преимуществ Axios по сравнению с традиционным API выборки.

  • При отправке запросов Axios автоматически указывает данные, но Fetch API требует, чтобы вы делали это вручную.
  • Axios обладает лучшей обработкой ошибок, чем Fetch API. Обработка ошибок с помощью Axios лучше, потому что неправильные ответы (такие как 404 или 500) в конечном итоге приведут к отклонению обещания путем создания исключения. Он может выдавать ошибки в диапазоне от 400 до 500. Эти ошибки можно легко обработать с помощью блока try-catch.
  • Axios позволяет отменять запросы и устанавливать тайм-ауты запросов. У него есть токен отмены, который можно использовать для отмены запроса.

Начало

Чтобы начать, вам необходимо установить Axios в свой проект. Вы можете сделать это, выполнив следующую команду в вашем проекте.

Как только это будет сделано, вы сможете импортировать Axios в свой проект и начать делать запросы.

import axios from 'axios';

axios.get('https://jsonplaceholder.typicode.com/todos/1')
  .then(response => {
    console.log(response);
  })
  .catch(error => {
    console.log(error);
  });

В приведенном выше коде мы делаем запрос GET к API. Мы используем метод get объекта axios для выполнения запроса. Метод get принимает URL API в качестве первого аргумента. Второй аргумент — это объект, содержащий конфигурацию запроса. Третий аргумент — это функция обратного вызова, которая вызывается при успешном выполнении запроса. Четвертый аргумент — это функция обратного вызова, которая вызывается при сбое запроса. Функции обратного вызова являются необязательными. Вы также можете использовать методы then и catch для обработки ответа и ошибок соответственно.

Для удобства использования мы всегда создаем экземпляр Axios и используем его для отправки запросов. Это потому, что мы можем установить baseUrl API и заголовки для всех запросов. Это упрощает отправку запросов к API. Ниже приведен пример того, как создать экземпляр Axios.

  1. Создайте файл в каталоге SRC вашего проекта react и назовите его client.js. Этот файл будет содержать экземпляр Axios.
  2. Используйте метод .create() объекта Axios для создания экземпляра Axios. Метод .create() принимает объект в качестве аргумента. Объект содержит конфигурацию для экземпляра Axios. Объект конфигурации может содержать следующие свойства.
  3. Свойство baseURL — это базовый URL-адрес API. Это URL-адрес, который будет использоваться для всех запросов. Свойство headers — это объект, содержащий заголовки для всех запросов. Объект headers может содержать любое количество заголовков. Объект headers является необязательным. Вы также можете установить заголовки для каждого запроса.

// src/client.js
import axios from 'axios';

const Client = axios.create({
  baseURL: 'https://jsonplaceholder.typicode.com', // base URL of the API
  headers: {
    'Content-Type': 'application/json',
  },
});

export default Client;

Оформление запроса

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

import Client from './client';

const App = () => {
  const [todos, setTodos] = useState([]);

  useEffect( async () => {
    const {data} = await Client.get('/todos');
    setTodos(data);
  }, []);

  return (
    <div>
      <h1>Todos</h1>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </div>
  );
};

В приведенном выше коде мы делаем запрос GET к API. Мы используем метод get экземпляра Axios для выполнения запроса. Здесь в качестве URL-адреса используется конечная точка /todos, поскольку для экземпляра уже установлен базовый URL-адрес.

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

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

Мы можем обрабатывать ошибки в Axios с помощью методов .then() и .catch() или с помощью блока try…catch для асинхронных/ожидающих запросов Axios.

Ниже приведен пример обработки ошибок с помощью блока try и catch.

import Client from './client';

const App = () => {
    const [todos, setTodos] = useState([]);
    const [error, setError] = useState(null);

    useEffect( async () => {
    try {
        const {data} = await Client.get('/todos');
        setTodos(data);
    } catch (error) {
        setError(error);
    }
    }, []);

};

Вывод

В этой статье мы рассмотрели Axios, стороннюю библиотеку HTTP-клиента на основе обещаний для node.js и браузера. Мы рассмотрели, как использовать Axios для выполнения HTTP-запросов к API. Мы также рассмотрели, как обрабатывать ошибки при выполнении HTTP-запросов.

React

Когда вы делаете вызов к бэкенд API с axios, вы должны рассмотреть, что делать с блоком .catch() вашего промиса. Теперь вам может показаться, что ваш API высокодоступен и он будет работать 24/7, что рабочий процесс пользователя довольно ясен, ваш JavaScript вменяем и у вас есть модульные тесты. Поэтому, когда вы смотрите на блок catch при выполнении запросов с помощью axios, вы можете подумать: “Ну… Я просто использую console.log. Все будет в порядке.”

Skillfactory.ru

axios.get('/my-highly-available-api')
  .then(response => { 
    // do stuff 
  }) 
  .catch(err => { 
    // what now? 
    console.log(err); 
  })

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

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

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

Отлов ошибок Axios

Ниже приведен фрагмент кода, который я начал включать в несколько проектов JS:

axios.post(url, data)
  .then(res => { 
    // do good things 
  }) 
  .catch(err => { 
    if (err.response) { 
      // client received an error response (5xx, 4xx)
    } else if (err.request) { 
      // client never received a response, or request never left 
    } else { 
      // anything else 
    } 
  })

Каждое условие предназначено для фиксации различного типа ошибки.

Проверка error.response

Если ваш объект error содержит поле response, это означает, что сервер ответил с ошибкой 4xx/5xx. Обычно это та ошибка, с которой мы лучше всего знакомы и с которой легче всего справиться.

Применяйте следующее: “Показать страницу 404 Not Found / сообщение об ошибке, если ваш API возвращает 404.” Покажите другое сообщение об ошибке, если ваш бэкенд возвращает 5xx или вообще ничего не возвращает. Вы может показаться, что ваш хорошо сконструированный бэкенд не будет генерировать ошибки, но это всего лишь вопрос времени, а не “если”.

Проверка error.request

Второй класс ошибок — это когда у вас нет ответа, но есть поле request, прикрепленное к ошибке. Когда же это происходит? Это происходит, когда браузер смог сделать запрос, но по какой-то причине не получил ответа. Это может произойти, если:

• Вы находитесь в обрывочной сети (например, в метро или используете беспроводную сеть здания).

• Ваш бэкенд зависает на каждом запросе и не возвращает ответ вовремя.

• Вы делаете междоменные запросы, но вы не авторизованы, чтобы их делать.

• Вы делаете междоменные запросы, и вы авторизованы, но бэкенд API возвращает ошибку.

Skillfactory.ru

Одна из наиболее распространенных версий этой ошибки имела бесполезное сообщение “Ошибка сети”. У нас есть API для фронтенда и бэкенда, размещенные в разных доменах, поэтому каждый вызов к бэкенд API — это междоменный запрос.

Из-за ограничений безопасности на JS в браузере, если вы делаете запрос API, и он не работает из-за плохих сетей, единственная ошибка, которую вы увидите — это “Ошибка сети”, которая невероятно бесполезна. Она может означать что угодно: от “Ваше устройство не имеет подключения к Интернету” до “Ваши OPTIONS вернули 5xx” (если вы делаете запросы CORS). Причина ошибки сети хорошо описана в этом ответе на StackOverflow.

Все остальные типы ошибок

Если ваш объект error не содержит поля response или request, это означает, что это не ошибка axios и, скорее всего, в вашем приложении что-то еще не так. Сообщение об ошибке + трассировка стека должны помочь вам понять, откуда оно исходит.

Как вам их исправить?

Ухудшение пользовательского опыта

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

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

Другой пример: если запрос на изображение профиля в потоке социальных сетей не выполняется, мы можем показать изображение-плейсхолдер и отключить изменения изображения профиля вместе с всплывающим уведомлением, объясняющим, почему кнопка “Обновить изображение профиля” отключена. Однако показывать предупреждение с надписью “422 необработанных объекта” бесполезно для пользователя.

Обрывистые сети

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

Для решения такого рода периодических проблем с сетью, мы добавили axios-retry, что решило большое количество ошибок, которые мы наблюдали в продакшне. Это было добавлено в нашу настройку axios:

const _axios = require('axios') 
const axiosRetry = require('axios-retry') 
const axios = _axios.create() 
// https://github.com/softonic/axios-retry/issues/87 const retryDelay = (retryNumber = 0) => { 
  const seconds = Math.pow(2, retryNumber) * 1000; 
  const randomMs = 1000 * Math.random(); 
  return seconds + randomMs; 
}; 
axiosRetry(axios, { 
  retries: 2, 
  retryDelay, 
  // retry on Network Error & 5xx responses 
  retryCondition: axiosRetry.isRetryableError, 
}); 
module.exports = axios;

Мы увидели, что 10% наших пользователей (которые находятся в плохих школьных сетях) периодически наблюдали ошибки сети, но число снизилось до <2% после добавления автоматических повторных попыток при сбое.

Скриншот количества ошибок сети, как они появляются в браузере New Relic. <1% запросов неверны. Это подводит меня к последнему пункту.

Добавляйте отчеты об ошибках в свой интерфейс

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

Другие инструменты, используемые нами— Sentry + SDK браузер, Rollbar и целая куча других полезных инструментов, перечисленных на GitHub.

Заключение

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

  • Проверьте, выполняете ли вы автоматические повторы, и, если нет, добавьте axios-retry.
  • Проверьте, что вы отлавливаете ошибки и сообщаете пользователю, что что-то произошло. Использовать только axios.get(...).catch(console.log) недостаточно.

Читайте также:

  • React TypeScript: Основы и лучшие практики
  • Первые шаги в анимации React Native
  • Как предотвратить состояние гонки с помощью React Context API

Перевод статьи Danny Perez: How to Handle API Errors in Your Web App Using Axios

Github: https://github.com/SeanningTatum/10-April-2021-Error-Handling

Slides: https://docs.google.com/presentation/d/1IT1d_hi2m1CiYVUwC5ubFM__Pych7nb_Cilhv8Hx7rU/edit?usp=sharing

Problem Statement

unknown error

Guess where this error came from? Upon first inspection — calling /api/items is the problem. But this raises a number of questions, such as

  • Which route called this? You might have inner routes.

  • Which file? My project is scaling and has over 100 possible files where that could’ve come from.

  • A 400 error can come in different shapes

    • Form validation error?

      • Which form value in particular? And what type of validation errors?
    • Problems with the Query Params?

    • etc.

better error

Now take a look at this error message.

  • Clear Error Type
  • Clear Error Message
  • Stack Trace is very clear, it tells you exactly which file and line the error was called
  • There’s a source map link you can click which lets you view the code in the browser.

If you click the link you’d see something like this

source map error

Now that’s more like it! Even people who are new the project can start debugging right away!

How do we add this?

Axios allows you to intercept requests and responses before they they are handled by .then and .catch. These are called interceptors (read more here).

If you’ve clicked the link you can create an instance and intercept the response like so.

// utils/axios.js

const instance = axios.create();

instance.interceptors.response.use(function (response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // Do something with response data
    return response;
}, function (error) {
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    return Promise.reject(error);
});

export default instance

So far this is the default error handling that axios is already providing us, let’s try meddling around with it to see how to play around with the interceptors.

instance.interceptors.response.use(function (response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // Do something with response data
    return response;
}, function (error) {
  
    if (error.code.status === 400) {
      return Promise.reject({
        message: "You've recieved an error!"
      })
    }
  
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    return Promise.reject(error);
});

Let’s see what that does in the console.

error object

Probably what you were expecting! It returned our custom object and even told us where the error was caught! Assuming you’ve wrapped it around a try/catch block or a .catch.

But this brings us to another problem — as we’re trying to scale and maintain our systems, from a coding point this is still not ‘simple enough’. We need to handle a lot of edge cases for errors, it’s like our data structures and algorithms class where we have to consider every edge case possible and can easily end up like this.

function fetchData() {
  try {
    const data = await axios.get('/data')
    
    return data
  } catch (err) {
    if (err.code === '404') {
      setError('Object does not exist.')
      return
    }
    
    if (err.name === 'err001-auth') {
      history.push('/login')
      return
    }
    
    if (err.body.validation.length > 0) {
      const validations = err.body.validation.map(err => err.message)
      setValidations(validations)
      return
    }
  }
}

At the moment it’s not hard to read or understand, but as errors get more complicated such as multiple permissions, handling errors from 3rd party apis and having different formats of errors it can get easily get out of hand if left unchecked. So what should we do? How can we make it readable in the code, abstract errors and easily debug?

function fetchData() {
  try {
    const data = await axios.get('/data')
    
    return data
  } catch (error) {
    if (error instanceof NotFoundError) {
      setError('Object does not exist.')
      return
    }
    
    if (error instanceof AuthorizationError) {
      // View what permissions were needed to access content
      console.log(error.permissions())
      
      history.push('/login')
      return
    }
    
    if (error instanceof ValidationError) {
      // Generate readable error message to display to user
      setError(error.generateErrorMessage())
      
      // Format Errors from Server
      setFormValueErrors(error.formatErrors())
      return
    }
    
    if (error instanceof ServerError) {
      history.push('/server-down')
      return
    }
  }
}

Now without even knowing anything about error codes and the different error conventions — I can read it what type of error it is in plain english and the error class has abstracted helper methods that I do not need to know about that are provided for me. Good for me 1 year later and any new developers that are joining the project!

Now let’s see how we can catch custom error objects. First we’re going to have to create one!

// utils/customErrors.js

export class BadRequestError extends Error {
  constructor(errors) {
    super('Something was wrong with your Request Body');
    this.name = 'BadRequestError';
    this.errors = errors || [];
  }

  // Assuming your data looks like this
  // {
  //   "errors": [
  //     {
  //       "location": "body",
  //       "msg": "Invalid value",
  //       "param": "username"
  //     },
  //     ...
  //   ]
  // }
  formatErrorsIntoReadableStr() {
    let str = 'There are formatting issues with';
    this.errors.forEach((error) => {
      str += `${error.param}, `;
    });

    return str;
  }
  
  // More custom code here if you want.
}

Now back into `utils/axios.js` let’s throw our custom error instead of a simple object

// utils/axios.js

// NEW:- Import New Error Class
import {BadRequestError} from './errors'

instance.interceptors.response.use(function (response) {
    // Any status code that lie within the range of 2xx cause this function to trigger
    // Do something with response data
    return response;
}, function (error) {
  
    if (error.code.status === 400) {
      // NEW:- Throw New Error
      throw new BadRequestError()
    }
  
    // Any status codes that falls outside the range of 2xx cause this function to trigger
    // Do something with response error
    return Promise.reject(error);
});
function fetchData() {
  try {
    const data = await axios.get('/data')
    
    return data
  } catch (error) { 
    // Catch your new error here!
    if (error instanceof BadRequestError) {
      // Generate readable error message to display to user
      setError(error.formatErrorsIntoReadableStr())
      return
    }
    
  }
}

Now with this simple code and some object oriented programming that you were probably taught in college, frontend life has become easier.

Disclaimer

This is not the best solution, is just a solution, there are probably better and more refined solutions on the web, but this one is simply a very simple one to implement.
Also here we are assuming you are using axios to fetch data from the client.

What’s the use case?

Let’s say that you have your frontend application that consumes some APIs, and also your APIs requires some authentication token, like a JWT token, to be sent at every request, and you got this token after a login screen for example.

And JWT token they generally have an expiration date, it could be a hour, a day, or more (but you shouldn’t use longer than that). Doesn’t matter if here we are talking about a refresh token or the actual token, but at some point the API you are calling might refuse your requests because the token expired.

One way to solve this problem, is to handle it when you do the request in your code, so if you have an error on your request, you just redirect it back to the login screen.
Something like this perhaps:

import axios from 'axios';

const fetchData = async () => {
  try {
    const { data } = await axios.get('some/endpoint');
    return data;
  } catch (error) {
    // this failed, so let's redirect to the login page
    console.log(error);
    window.location.href = '/';
  }
}

Enter fullscreen mode

Exit fullscreen mode

And the above solution is ok, if you do only one request on your page it could work.

But, this also mean that if you have multiple pages, and maybe in every page you do multiple requests, this strategy becomes a bit cumbersome.

Use axios interceptors instead!

A better and simple way to handle the same problem, in a centrealized way, is to use axios interceptors instead.

With interceptors you can hook to a specific lifecycle of the API call, the request and response, and maybe modify the behaviours of them.

The axios.intercepotrs.request.use(config) function has one argument, which is the configuration of the headers, while the axios.intercepotrs.response.use(response, error) has two, which hooks with the .then, or a successful response, and the .catch, when we get an Error (any status that is not 2xx) as a response.

For example on the example below, we’ll tell to axios to execute the code on every requests we do:

import axios from 'axios';

axios.interceptors.response.use(
  response => response,
  error => {
    window.location.href = '/';
  });

Enter fullscreen mode

Exit fullscreen mode

As you see above, we do nothing to the response, but if the error is invoked, we redirect to our login page.

Note: you don’t need to do this on every file, you just need to do it once, like on a configuration file for example.

If you want to have better control, like you want to target only some specific http status codes, let’s say the 401 Unauthorized, you can access to that via the error.response.status, so our code will look like this:

axios.interceptors.response.use(
  response => response,
  error => {
    if (error.response.status === 401) {
      window.location.href = '/';
    }
  });

Enter fullscreen mode

Exit fullscreen mode

Do you want to handle this only for some requests?

Well, then you can also create an axios instance and use that instance only for some calls, for example:

// lib/customAxios.js
export const customAxios = axios.create({
  baseURL: 'http://yourcoolapi.com/api',
  headers: {
    'X-Custom-Header': 'foobar'
  }
});

customAxios.interceptors.response.use(
  response => response,
  error => {
    if (error.response.status === 401) {
      window.location.href = '/';
    }
  });

export default customAxios;

// yourcode.js
import customAxios from '/lib/customAxios.js';

const fetchData = async () => {
  try {
    const { data } = await customAxios.get('some/endpoint');
    return data;
  } catch (error) {
    // this failed, so let's redirect to the login page
    console.log(error);
  }
}

Enter fullscreen mode

Exit fullscreen mode

Again, this is a very simple use case on how to use axios interceptors, there could be different strategies that works as well or better than this one.
Another one could be to use the request interceptor, check the JWT token even before we actually call the API, and then request a new token, or redirect to the login, or else.
But the one I’ve explained in this post is probably the easiest to grasp and handle.

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

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

Этот материал, посвящённый обработке ошибок в JavaScript, разбит на три части. Сначала мы сделаем общий обзор системы обработки ошибок в JavaScript и поговорим об объектах ошибок. После этого мы поищем ответ на вопрос о том, что делать с ошибками, возникающими в серверном коде (в частности, при использовании связки Node.js + Express.js). Далее — обсудим обработку ошибок в React.js. Фреймворки, которые будут здесь рассматриваться, выбраны по причине их огромной популярности. Однако рассматриваемые здесь принципы работы с ошибками универсальны, поэтому вы, даже если не пользуетесь Express и React, без труда сможете применить то, что узнали, к тем инструментам, с которыми работаете.

Код демонстрационного проекта, используемого в данном материале, можно найти в этом репозитории.

1. Ошибки в JavaScript и универсальные способы работы с ними

Если в вашем коде что-то пошло не так, вы можете воспользоваться следующей конструкцией.

throw new Error('something went wrong')

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

Начинающие JS-программисты обычно не используют инструкцию throw. Они, как правило, сталкиваются с исключениями, выдаваемыми либо средой выполнения языка, либо сторонними библиотеками. Когда это происходит — в консоль попадает нечто вроде ReferenceError: fs is not defined и выполнение программы останавливается.

▍Объект Error

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

const myError = new Error('please improve your code')
console.log(myError.message) // please improve your code

Второе свойство объекта, очень важное, представляет собой трассировку стека ошибки. Это — свойство stack. Обратившись к нему можно просмотреть стек вызовов (историю ошибки), который показывает последовательность операций, приведшую к неправильной работе программы. В частности, это позволяет понять — в каком именно файле содержится сбойный код, и увидеть, какая последовательность вызовов функций привела к ошибке. Вот пример того, что можно увидеть, обратившись к свойству stack.

Error: please improve your code
 at Object.<anonymous> (/Users/gisderdube/Documents/_projects/hacking.nosync/error-handling/src/general.js:1:79)
 at Module._compile (internal/modules/cjs/loader.js:689:30)
 at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
 at Module.load (internal/modules/cjs/loader.js:599:32)
 at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
 at Function.Module._load (internal/modules/cjs/loader.js:530:3)
 at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
 at startup (internal/bootstrap/node.js:266:19)
 at bootstrapNodeJSCore (internal/bootstrap/node.js:596:3)

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

▍Генерирование и обработка ошибок

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

▍Конструкция try…catch

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

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

const a = 5

try {
    console.log(b) // переменная b не объявлена - возникает ошибка
} catch (err) {
    console.error(err) // в консоль попадает сообщение об ошибке и стек ошибки
}

console.log(a) // выполнение скрипта не останавливается, данная команда выполняется

Если бы в этом примере мы не заключили бы сбойную команду console.log(b) в блок try...catch, то выполнение скрипта было бы остановлено.

▍Блок finally

Иногда случается так, что некий код нужно выполнить независимо от того, произошла ошибка или нет. Для этого можно, в конструкции try...catch, использовать третий, необязательный, блок — finally. Часто его использование эквивалентно некоему коду, который идёт сразу после try...catch, но в некоторых ситуациях он может пригодиться. Вот пример его использования.

const a = 5

try {
    console.log(b) // переменная b не объявлена - возникает ошибка
} catch (err) {
    console.error(err) // в консоль попадает сообщение об ошибке и стек ошибки
} finally {
    console.log(a) // этот код будет выполнен в любом случае
}

▍Асинхронные механизмы — коллбэки

Программируя на JavaScript всегда стоит обращать внимание на участки кода, выполняющиеся асинхронно. Если у вас имеется асинхронная функция и в ней возникает ошибка, скрипт продолжит выполняться. Когда асинхронные механизмы в JS реализуются с использованием коллбэков (кстати, делать так не рекомендуется), соответствующий коллбэк (функция обратного вызова) обычно получает два параметра. Это нечто вроде параметра err, который может содержать ошибку, и result — с результатами выполнения асинхронной операции. Выглядит это примерно так:

myAsyncFunc(someInput, (err, result) => {
    if(err) return console.error(err) // порядок работы с объектом ошибки мы рассмотрим позже
    console.log(result)
})

Если в коллбэк попадает ошибка, она видна там в виде параметра err. В противном случае в этот параметр попадёт значение undefined или null. Если оказалось, что в err что-то есть, важно отреагировать на это, либо так как в нашем примере, воспользовавшись командой return, либо воспользовавшись конструкцией if...else и поместив в блок else команды для работы с результатом выполнения асинхронной операции. Речь идёт о том, чтобы, в том случае, если произошла ошибка, исключить возможность работы с результатом, параметром result, который в таком случае может иметь значение undefined. Работа с таким значением, если предполагается, например, что оно содержит объект, сама может вызвать ошибку. Скажем, это произойдёт при попытке использовать конструкцию result.data или подобную ей.

▍Асинхронные механизмы — промисы

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

(node:7741) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: something went wrong
(node:7741) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code. */

В результате можно порекомендовать всегда, при работе с промисами, использовать блок 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

После того, как в JavaScript появилась конструкция async/await, мы вернулись к классическому способу обработки ошибок — к try...catch...finally. Обрабатывать ошибки при таком подходе оказывается очень легко и удобно. Рассмотрим пример.

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

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

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

2. Генерирование и обработка ошибок в серверном коде

Теперь, когда у нас есть инструменты для работы с ошибками, посмотрим на то, что мы можем с ними делать в реальных ситуациях. Генерирование и правильная обработка ошибок — это важнейший аспект серверного программирования. Существуют разные подходы к работе с ошибками. Здесь будет продемонстрирован подход с использованием собственного конструктора для экземпляров объекта Error и кодов ошибок, которые удобно передавать во фронтенд или любым механизмам, использующим серверные API. Как структурирован бэкенд конкретного проекта — особого значения не имеет, так как при любом подходе можно использовать одни и те же идеи, касающиеся работы с ошибками.

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

  1. Универсальная обработка ошибок — некий базовый механизм, подходящий для обработки любых ошибок, в ходе работы которого просто выдаётся сообщение наподобие Something went wrong, please try again or contact us, предлагающее пользователю попробовать выполнить операцию, давшую сбой, ещё раз или связаться с владельцем сервера. Эта система не отличается особой интеллектуальностью, но она, по крайней мере, способна сообщить пользователю о том, что что-то пошло не так. Подобное сообщение гораздо лучше, чем «бесконечная загрузка» или нечто подобное.
  2. Обработка конкретных ошибок — механизм, позволяющий сообщить пользователю подробные сведения о причинах неправильного поведения системы и дать ему конкретные советы по борьбе с неполадкой. Например, это может касаться отсутствия неких важных данных в запросе, который пользователь отправляет на сервер, или в том, что в базе данных уже существует некая запись, которую он пытается добавить ещё раз, и так далее.

▍Разработка собственного конструктора объектов ошибок

Здесь мы воспользуемся стандартным классом Error и расширим его. Пользоваться механизмами наследования в JavaScript — дело рискованное, но в данном случае эти механизмы оказываются весьма полезными. Зачем нам наследование? Дело в том, что нам, для того, чтобы код удобно было бы отлаживать, нужны сведения о трассировке стека ошибки. Расширяя стандартный класс Error, мы, без дополнительных усилий, получаем возможности по трассировке стека. Мы добавляем в наш собственный объект ошибки два свойства. Первое — это свойство code, доступ к которому можно будет получить с помощью конструкции вида err.code. Второе — свойство status. В него будет записываться код состояния HTTP, который планируется передавать клиентской части приложения.

Вот как выглядит класс CustomError, код которого оформлен в виде модуля.

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 = route(req) // Передаём запрос функции route
            res.send(result) // Передаём клиенту то, что получено от функции route
        } catch (err) {
            /*
            Сюда мы попадаем в том случае, если в функции 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 { 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 =>... и так далее. Эта схема работает и при использовании параметров запросов. Например — для запроса вида GET /city?startsWith=R. В целом, здесь продемонстрировано, что при обработке ошибок, во фронтенд может попасть либо общая ошибка, содержащая лишь предложение попробовать снова или связаться с владельцем сервера, либо ошибка, сформированная с использованием конструктора CustomError, которая содержит подробные сведения о проблеме.
Данные общей ошибки придут в клиентскую часть приложения в таком виде:

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

Конструктор CustomError используется так:

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

Это даёт следующий JSON-код, передаваемый во фронтенд:

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

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

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

3. Работа с ошибками на клиенте

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

▍Сохранение сведений об ошибках в состоянии приложения

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

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

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

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

Здесь будет использоваться встроенная в React система управления состоянием приложения, но, при необходимости, вы можете воспользоваться и специализированными решениями для управления состоянием — такими, как MobX или Redux.

▍Глобальные ошибки

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

Сообщение о глобальной ошибке

Теперь взглянем на код, который хранится в файле Application.js.

import React, { Component } from 'react'

import GlobalError from './GlobalError'

class Application extends Component {
    constructor(props) {
        super(props)

        this.state = {
            error: '',
        }

        this._resetError = this._resetError.bind(this)
        this._setError = this._setError.bind(this)
    }

    render() {
        return (
            <div className="container">
                <GlobalError error={this.state.error} resetError={this._resetError} />
                <h1>Handling Errors</h1>
            </div>
        )
    }

    _resetError() {
        this.setState({ error: '' })
    }

    _setError(newError) {
        this.setState({ error: newError })
    }
}

export default Application

Как видно, в состоянии, в Application.js, имеется место для хранения данных ошибки. Кроме того, тут предусмотрены методы для сброса этих данных и для их изменения.

Ошибка и метод для сброса ошибки передаётся компоненту GlobalError, который отвечает за вывод сообщения об ошибке на экран и за сброс ошибки после нажатия на значок x в поле, где выводится сообщение. Вот код компонента GlobalError (файл GlobalError.js).

import React, { Component } from 'react'

class GlobalError extends Component {
    render() {
        if (!this.props.error) return null

        return (
            <div
                style={{
                    position: 'fixed',
                    top: 0,
                    left: '50%',
                    transform: 'translateX(-50%)',
                    padding: 10,
                    backgroundColor: '#ffcccc',
                    boxShadow: '0 3px 25px -10px rgba(0,0,0,0.5)',
                    display: 'flex',
                    alignItems: 'center',
                }}
            >
                {this.props.error}
                 
                <i
                    className="material-icons"
                    style={{ cursor: 'pointer' }}
                    onClick={this.props.resetError}
                >
                    close
                </font></i>
            </div>
        )
    }
}

export default GlobalError

Обратите внимание на строку if (!this.props.error) return null. Она указывает на то, что при отсутствии ошибки компонент ничего не выводит. Это предотвращает постоянный показ красного прямоугольника на странице. Конечно, вы, при желании, можете поменять внешний вид и поведение этого компонента. Например, вместо того, чтобы сбрасывать ошибку по нажатию на x, можно задать тайм-аут в пару секунд, по истечении которого состояние ошибки сбрасывается автоматически.

Теперь, когда всё готово для работы с глобальными ошибками, для задания глобальной ошибки достаточно воспользоваться _setError из Application.js. Например, это можно сделать в том случае, если сервер, после обращения к нему, вернул сообщение об общей ошибке (error: 'GENERIC'). Рассмотрим пример (файл GenericErrorReq.js).

import React, { Component } from 'react'
import axios from 'axios'

class GenericErrorReq extends Component {
    constructor(props) {
        super(props)

        this._callBackend = this._callBackend.bind(this)
    }

    render() {
        return (
            <div>
                <button onClick={this._callBackend}>Click me to call the backend</button>
            </div>
        )
    }

    _callBackend() {
        axios
            .post('/api/city')
            .then(result => {
                // сделать что-нибудь с результатом в том случае, если запрос оказался успешным
            })
            .catch(err => {
                if (err.response.data.error === 'GENERIC') {
                    this.props.setError(err.response.data.description)
                }
            })
    }
}

export default GenericErrorReq

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

▍Обработка специфических ошибок, возникающих при выполнении запросов

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

Сообщение о специфической ошибке

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

import React, { Component } from 'react'
import axios from 'axios'

import InlineError from './InlineError'

class SpecificErrorRequest extends Component {
    constructor(props) {
        super(props)

        this.state = {
            error: '',
        }

        this._callBackend = this._callBackend.bind(this)
    }

    render() {
        return (
            <div>
                <button onClick={this._callBackend}>Delete your city</button>
                <InlineError error={this.state.error} />
            </div>
        )
    }

    _callBackend() {
        this.setState({
            error: '',
        })

        axios
            .delete('/api/city')
            .then(result => {
                // сделать что-нибудь с результатом в том случае, если запрос оказался успешным
            })
            .catch(err => {
                if (err.response.data.error === 'GENERIC') {
                    this.props.setError(err.response.data.description)
                } else {
                    this.setState({
                        error: err.response.data.description,
                    })
                }
            })
    }
}

export default SpecificErrorRequest

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

▍Ошибки, возникающие в клиентской части приложения

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

В поле ничего нет, мы сообщаем об этом пользователю

Вот код файла SpecificErrorFrontend.js, реализующий вышеописанный функционал.

import React, { Component } from 'react'
import axios from 'axios'

import InlineError from './InlineError'

class SpecificErrorRequest extends Component {
    constructor(props) {
        super(props)

        this.state = {
            error: '',
            city: '',
        }

        this._callBackend = this._callBackend.bind(this)
        this._changeCity = this._changeCity.bind(this)
    }

    render() {
        return (
            <div>
                <input
                    type="text"
                    value={this.state.city}
                    style={{ marginRight: 15 }}
                    onChange={this._changeCity}
                />
                <button onClick={this._callBackend}>Delete your city</button>
                <InlineError error={this.state.error} />
            </div>
        )
    }

    _changeCity(e) {
        this.setState({
            error: '',
            city: e.target.value,
        })
    }

    _validate() {
        if (!this.state.city.length) throw new Error('Please provide a city name.')
    }

    _callBackend() {
        this.setState({
            error: '',
        })

        try {
            this._validate()
        } catch (err) {
            return this.setState({ error: err.message })
        }

        axios
            .delete('/api/city')
            .then(result => {
                // сделать что-нибудь с результатом в том случае, если запрос оказался успешным
            })
            .catch(err => {
                if (err.response.data.error === 'GENERIC') {
                    this.props.setError(err.response.data.description)
                } else {
                    this.setState({
                        error: err.response.data.description,
                    })
                }
            })
    }
}

export default SpecificErrorRequest

▍Интернационализация сообщений об ошибках с использованием кодов ошибок

Возможно, сейчас вы задаётесь вопросом о том, зачем нам нужны коды ошибок (наподобие GENERIC), если мы показываем пользователю только сообщения об ошибках, полученных с сервера. Дело в том, что, по мере роста и развития приложения, оно, вполне возможно, выйдет на мировой рынок, а это означает, что настанет время, когда создателям приложения нужно будет задуматься о поддержке им нескольких языков. Коды ошибок позволяют отличать их друг от друга и выводить сообщения о них на языке пользователя сайта.

Итоги

Надеемся, теперь у вас сформировалось понимание того, как можно работать с ошибками в веб-приложениях. Нечто вроде console.error(err) следует использовать только в отладочных целях, в продакшн подобные вещи, забытые программистом, проникать не должны. Упрощает решение задачи логирования использование какой-нибудь подходящей библиотеки наподобие loglevel.

Уважаемые читатели! Как вы обрабатываете ошибки в своих проектах?

In the course of fetching APIs, if the API request doesn’t go as planned, we may encounter errors. Let’s see how to manage these errors using Axios.

If you don’t want to use the built-in Fetch API, you can opt for the many 3rd party libraries available on npm, and Axios is the most popular among them. It is essentially a wrapper around the Fetch API and allows you to make HTTP requests using a promise-based HTTP client.

API Errors

When a request to an API doesn’t go as planned, an API error occurs. So, the API must respond to the client specifying whether the request was successful or not. In this case, we should send an error response, and it is the only way for the developers to diagnose what went wrong. HTTP Status Codes are used for this purpose. The following status codes notify about the errors.

  • 4xx — Client error. Such as 404: Requested URL not found.
  • 5xx — Server error.

Informing the client about the error helps them understand the error and its cause.

Using Axios

To handle errors in a standard API call using Axios, we use a try...catch block. For example, take a look at the following code, which fetches random quotes from the Famous Quotes API from RapidAPI Hub.

js

import axios from 'axios';

const fetchQuotes = async () => {

try {

const res = await axios.get(

`https://famous-quotes4.p.rapidapi.com/random`

);

} catch (error) {

// Do something with the error here

}

};

If an error occurs, the catch block captures it. We need to add some logic in this block to handle the errors. We have to take care of three scenarios of errors:

  1. Request is made, but the server responds with an error.

  2. Request is made, but no response is received from the server.

  3. When an error occurs while setting up the request.

To handle these scenarios, we can use an if-else block like this:

js

try {

const res = await axios.get(`https://famous-quotes4.p.rapidapi.com/random`);

} catch (error) {

if (error.response) {

// Request made but the server responded with an error

} else if (error.request) {

// Request made but no response is received from the server.

} else {

// Error occured while setting up the request

}

}

It is critical to check for the request and response properties because there will be no response property if we do not receive a response. Similarly, there will be no request property if the request is not set up. Let’s take a look at these properties.

error.response

If the request is made and the server gives an error response, the error object will have a response property. It means that a 4XX or 5XX error has occurred. The response object has many properties which we can log, like the status property, which has the status code of the error.

error.request

error.request is the request object of the HTTP request that the client made. It contains information such as the HTTP method, URL, and the headers sent with the request. For Axios, it is an instance of XMLHttpRequest when running in the browser and an instance of http.ClientRequest when executed in Node.js. We use it when we do not receive a valid response from the API due to a poor network or unauthorized access.

Logging Errors

Finally, we can use these properties to log errors properly. It will look like this in code:

js

try {

const res = await axios.get(`https://famous-quotes4.p.rapidapi.com/random`);

} catch (error) {

if (error.response) {

// Request made but the server responded with an error

console.log(error.response.data);

console.log(error.response.status);

console.log(error.response.headers);

} else if (error.request) {

// Request made but no response is received from the server.

console.log(error.request);

} else {

// Error occured while setting up the request

console.log('Error', error.message);

}

}

The same goes for Axios POST, PUT, PATCH, and all other types of requests. Now you know how to manage API errors using Axios. Find a suitable API from RapidAPI Hub and integrate it into your projects using Axios.

Axios — это библиотека JS, предоставляющая простой в применении интерфейс для выполнения HTTP-запросов в браузере и на стороне сервера. Она развернута на стандарте Promise API и предназначена для работы с асинхронными запросами. 

Библиотека широко используется в различных проектах, включая React, Vue.js, Angular и другие фреймворки. Она позволяет разработчикам легко взаимодействовать с API и обрабатывать данные, получаемые от сервера.

Список преимуществ применения данной библиотеки в React:

  • Простота применения: Имеет простой и легко понятный интерфейс, который делает его ясным в применении даже среди новичков. Это позволяет разработчикам быстро отправлять HTTP-запросы на сервер и получать от него ответы.
  • Универсальность: Поддерживает не только браузеры, но и Node.js, что делает его идеальным выбором для применения на клиентской и на серверной стороне в проектах на React.
  • Поддержка Promises: Возвращает объект Promises, что делает его идеальным для работы с современным синтаксисом JavaScript и облегчает обработку асинхронных операций.
  • Интерсепторы: Позволяет использовать интерсепторы, чтобы запросы и ответы обрабатывались до их отправки и после получения. Это полезно для добавления заголовков, обработки ошибок и многого другого.
  • Отмена запроса: Библиотека допускает отмену запросов, что позволяет улучшить производительность приложения и предотвратить лишние запросы.
  • Поддержка заголовков и параметров запроса: Позволяет добавлять заголовки и параметры запроса, что полезно при передаче информации на сервер.
  • Широкий функционал: Поддерживает все основные методы HTTP-запросов: GET, POST, PUT, DELETE и т.д. Он также поддерживает множество типов данных, таких как JSON, формы и текст.
  • Обработка ошибок: Имеет встроенную обработку ошибок, что позволяет определять и обрабатывать ошибки, возникающие во время выполнения запросов.

Стоит сказать о требованиях, достаточных для работы с рассматриваемой библиотекой:

  • Node.js 14 версии и позднее;
  • Проект на React, организованный инструментом create-react-app;
  • Базовые знания JS, HTML и CSS.

В статье мы рассмотрим, как использовать Axios с React и приведем несколько примеров его применения.

Шаг 1. Перед началом работы с Axios необходимо выбрать рабочий проект:

cd project_name

Либо организовать новый, а затем перейти в него:

npx create-react-app project_name
cd project_name

Создадим React-приложение с названием timeweb. Результаты создания продемонстрированы на картинке ниже.

Image1

Шаг 2. Далее необходимо поставить библиотеку, воспользовавшись пакетным менеджером npm. Для этого выполним следующую команду в терминале:

npm install axios

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

import axios from 'axios';

После успешной установки и импортирования библиотеки Axios в ваше React-приложение вы можете начать взаимодействие с ней.

GET-запрос

Чтобы отправить GET-запрос, используется метод axios.get(), который принимает один параметр – адрес интернет-ресурса. Он не должен использоваться для отправки конфиденциальных данных, так как они могут быть видны в адресной строке браузера и логах сервера.

Для начала разработаем необходимый для реализации компонент. 

Шаг 1. Перейдем из каталога приложения в директорию src.

cd src

Шаг 2. Далее создаем файл с расширением .js:

nano Test1.js

Шаг 3. Теперь переходим к написанию кода:

import React, { useEffect, useState } from "react";
import axios from "axios";

function Test1() {
  const [data, setData] = useState([]);

  useEffect(() => {
    axios
      .get("https://jsonplaceholder.typicode.com/users")
      .then((response) => {
        setData(response.data);
      })
      .catch((error) => {
        console.log(error);
      });
  }, []);

  return (
    <div>
      <h1>Список пользователей:</h1>
      <ul>
        {data.map((user) => (
          <li key={user.id}>
            {user.name} ({user.email})
          </li>
        ))}
      </ul>
    </div>
  );
}

export default Test1;

В данном примере Test1 отправляет GET-запрос на указанный сервис и отображает список пользователей на странице. В компоненте используются два хука: 

  • useEffect для выполнения запроса; 
  • useState для хранения данных, полученных от API. 

Хук — это функция, которая позволяет использовать состояние и другие возможности React в функциональных компонентах. Если запрос не получилось отправить или обработать, то мы выводим ошибку в консоль.

Шаг 4. Проверим результат запроса. Он представлен на картинке ниже.

Image5

Обратите внимание, что в данном примере и во всех остальных используется API https://jsonplaceholder.typicode.com, который предоставляет фейковые данные с целью тестирования. Вам следует заменить его на тот, который будет использоваться в проекте.

POST-запрос

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

POST-запросы могут быть безопасными, если они не изменяют состояние сервера, но они не являются идемпотентными, что означает, что при повторном отправлении может произойти изменение состояния сервера. Для идемпотентности они могут содержать специальный заголовок, например, Idempotency-Key, который позволяет серверу определять, был ли запрос отправлен ранее.

POST-запросы также могут быть защищены с помощью токенов CSRF, чтобы предотвратить их подделку со стороны злоумышленников.

Чтобы отправить POST-запрос, необходимо вызвать метод post() и передать ему данные и адрес интернет-ресурса, на который необходимо их отправить. 

Шаг 1. Откроем ранее созданный файл для редактирования:

nano Test1.js

Шаг 2. Напишем новый код:

import React, { useState } from "react";
import axios from "axios";

function Test1() {
  const [data, setData] = useState({ name: "", email: "" });
  const [response, setResponse] = useState("");

  const handleChange = (event) => {
    setData({ ...data, [event.target.name]: event.target.value });
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    axios
      .post("https://jsonplaceholder.typicode.com/users", data)
      .then((response) => {
        setResponse(response.data);
      })
      .catch((error) => {
        console.log(error);
      });
  };

  return (
    <div>
      <h1>Отправка данных на сервер</h1>
      <form onSubmit={handleSubmit}>
        <label>
          Имя:
          <input
            type="text"
            name="name"
            value={data.name}
            onChange={handleChange}
          />
        </label>
        <br />
        <label>
          Email:
          <input
            type="email"
            name="email"
            value={data.email}
            onChange={handleChange}
          />
        </label>
        <br />
        <button type="submit">Отправить</button>
      </form>
      {response && (
        <p>
          Данные успешно отправлены: {response.name} ({response.email})
        </p>
      )}
    </div>
  );
}

export default Test1;

В этом примере мы также применили useState для хранения данных, введенных пользователем в форме, и данных, полученных от API. Мы также создали две функции: 

  • handleChange для обновления состояния формы при изменении пользователем данных;
  • handleSubmit для отправки POST-запроса при передаче формы.

Когда форма передается, мы отправляем POST-запрос и обрабатываем полученные данные, используя метод setResponse. Если запрос не удалось отправить или обработать, то мы выводим ошибку в консоль.

Шаг 3. Проверим результат работы компонента. Он представлен на картинке ниже.

Image2

PUT-запрос

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

Чтобы отправить PUT-запрос, необходимо вызвать метод put() и передать ему данные и URL-адрес ресурса, на который необходимо их отправить. 

Шаг 1. Откроем файл компонента для редактирования:

nano Test1.js

Шаг 2. Снова перепишем код файла Test1.js:

import React, { useState } from "react";
import axios from "axios";

const Test1 = () => {
  const [postData, setPostData] = useState({ id: "", title: "", body: "" });
  const [putData, setPutData] = useState({ id: "", title: "", body: "" });

  const handleInputChange = (event) => {
    setPostData({ ...postData, [event.target.name]: event.target.value });
  };

  const handleSubmit = (event) => {
    event.preventDefault();
    axios
      .put(`https://jsonplaceholder.typicode.com/posts/${postData.id}`, {
        title: postData.title,
        body: postData.body,
      })
      .then((response) => {
        setPutData(response.data);
        console.log(response);
      })
      .catch((error) => {
        console.log(error);
      });
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="id">Уникальный номер: </label>
          <input
            type="text"
            name="id"
            value={postData.id}
            onChange={handleInputChange}
          />
        </div>
        <div>
          <label htmlFor="title">Заголовок: </label>
          <input
            type="text"
            name="title"
            value={postData.title}
            onChange={handleInputChange}
          />
        </div>
        <div>
          <label htmlFor="body">Основная информация: </label>
          <input
            type="text"
            name="body"
            value={postData.body}
            onChange={handleInputChange}
          />
        </div>
        <button type="submit">Обновить данные</button>
      </form>
      <div>
        <p>Ответ:</p>
        <p>Уникальный номер:: {putData.id}</p>
        <p>Заголовок: {putData.title}</p>
        <p>Основная информация: {putData.body}</p>
      </div>
    </div>
  );
};

export default Test1;

Здесь используется useState для управления двумя состояниями: 

  • postData содержит данные для отправки в PUT-запросе; 
  • putData содержит полученные в ответ на запрос данные.

Также определяем две функции: 

  • handleInputChange — обновляет состояние postData, при добавлении пользователем данных в форму;
  • handleSubmit — посылает PUT-запрос и обновляет состояние putData.

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

Шаг 3. Проверим результат работы компонента. Он представлен на картинке ниже.

Image4

DELETE-запрос

Чтобы отправить DELETE-запрос, нужно вызвать метод delete() и передать ему URL-адрес ресурса для удаления. После получения запроса сервер проверяет, есть ли указанный ресурс на сервере, и если он есть, то удаляет его. Если ресурса на сервере нет, он может вернуть ошибку 404 «Не найдено».

Шаг 1. Откроем файл компонента для редактирования:

nano Test1.js

Шаг 2. Снова перепишем код Test1.js:

import React, { useState } from 'react';
import axios from 'axios';

const Test1 = () => {
  const [postId, setPostId] = useState(null);
  const [response, setResponse] = useState(null);

  const handleDelete = () => {
    axios.delete(`https://jsonplaceholder.typicode.com/posts/${postId}`)
      .then(res => setResponse(res))
      .catch(err => setResponse(err));
  }

  return (
    <div>
      <input type="number" placeholder="Enter Post ID" onChange={(e) => setPostId(e.target.value)} />
      <button onClick={handleDelete}>Delete Post</button>
      {response && (
        <div>
          <h2>Response</h2>
          <p>Status: {response.status}</p>
          <p>Data: {JSON.stringify(response.data)}</p>
        </div>
      )}
    </div>
  );
}

export default Test1;

В этом компоненте мы создали форму, где пользователь может ввести ID записи, которую он хочет удалить, и отправить запрос на удаление при помощи метода axios.delete(). Мы также обработали успешный ответ и ошибки при помощи методов then() и catch() соответственно.

Шаг 3. Проверим результат работы компонента. Он представлен на картинке ниже.

Image6

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

Axios предоставляет удобный способ обработки ошибок, возникающих при отправке HTTP-запросов. then() и catch() принимают функции, которые будут выполнены в зависимости от статуса выполнения запроса (выполнен или произошла ошибка). 

Шаг 1. Откроем файл компонента для редактирования:

nano Test1.js

Шаг 2. В очередной раз перепишем код файла Test1.js:

import React, { useState } from "react";
import axios from "axios";

const Test1 = () => {
  const [error, setError] = useState(null);

  const handleButtonClick = () => {
    axios
      .get("https://jsonplaceholder.typicode.com/posts/invalid-url")
      .then((response) => {
        console.log(response);
      })
      .catch((error) => {
        setError(error);
      });
  };

  return (
    <div>
      <button onClick={handleButtonClick}>Отправить GET-запрос</button>
      {error && (
        <div>
          <h2>Сообщение об ошибке:</h2>
          <p>{error.message}</p>
        </div>
      )}
    </div>
  );
};

export default Test1;

В примере выше создается состояние error. Оно позволяет отслеживать ошибки, которые возникают при отправке запросов. Затем мы создаем функцию handleButtonClick, которая отправляет GET-запрос на неверный URL и, в случае возникновения ошибки, использует метод setError для обновления состояния error с сообщением об ошибке.

Возвращаемое значение компонента содержит кнопку и блок данных. Кнопка вызывает функцию handleButtonClick, а блок отображается только в том случае, если error не является нулевым или ложным. В блоке мы выводим сообщение об ошибке, используя свойство message объекта ошибки.

Шаг 3. Проверим результат работы компонента. Он представлен на картинке ниже.

Image3

Кроме того, рассматриваемая библиотека также предоставляет статический метод isCancel(), который может быть использован для проверки, является ли ошибка результатом отмены запроса. Это может быть полезно, если вы используете CancelToken или AbortController для отмены запроса. Об этом мы поговорим немного позже.

Создание базового экземпляра

Базовый экземпляр — это объект, который содержит конфигурационные параметры и методы для создания запросов.

Чтобы организовать базовый экземпляр необходимо использовать create() из рассматриваемой библиотеки и передать ей объект с настройками. В объекте настроек можно указать общий URL для всех запросов, заголовки, авторизационный токен и другие параметры.

Например, если в приложении нужно отправлять запросы к API с общим адресом ресурса, можно создать базовый экземпляр в директории src следующим образом.

Шаг 1. Создадим файл с расширением .js:

nano basic.js

Шаг 2. Далее напишем код:

import axios from 'axios';

const exemplar = axios.create({
  baseURL: 'https://jsonplaceholder.typicode.com/',
});

export default exemplar;

Шаг 3. Импортирование экземпляра рассмотрим на примере из раздела «Обработка ошибок»:

import React, { useState } from "react";
import axios from "axios";
import exemplar from "./basic";

const Test1 = () => {
  const [error, setError] = useState(null);

  const handleButtonClick = () => {
    exemplar
      .get("invalid-url")
      .then((response) => {
        console.log(response);
      })
      .catch((error) => {
        setError(error);
      });
  };

...

export default Test1;

Изменения — в строках:

    exemplar
      .get("invalid-url")

и

import exemplar from "./basic";

Использование async и await

Async и Await — это инструменты для управления асинхронными операциями в JavaScript. Они могут значительно упростить код, связанный с обработкой ответов на запросы с помощью Axios в React.

Для использования данных инструментов вам нужно сначала определить функцию, которая будет вызываться асинхронно. Затем вы можете использовать ключевое слово await, чтобы вызвать функцию Axios и дождаться, пока она выполнит запрос.

Перепишем код из раздела про GET-запрос:

import React, { useEffect, useState } from "react";
import axios from "axios";

function Test1() {
    const [data, setData] = useState([]);

    useEffect(() => {
        async function asyncFunction() {
            try {
                const response = await axios.get("https://jsonplaceholder.typicode.com/users");
                setData(response.data);
            } catch (error) {
                console.log(error);
            }
        }
        asyncFunction();
    }, []);

return (
<div>
    <h1>Список пользователей:</h1>
    <ul>
        {data.map((user) => (
        <li key={user.id}>
            {user.name} ({user.email})
        </li>
        ))}
    </ul>
</div>
);
}

export default Test1;

Создается асинхронная функция asyncFunction, которая ожидает ответа от сервера с помощью ключевого слова await и устанавливает полученные данные с помощью setData

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

Отмена запросов

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

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

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

Актуальный способ отмены запросов — использование стандартного API AbortController, который был добавлен в браузеры в ECMAScript 2017. Этот API предоставляет удобный механизм для отмены любых асинхронных операций, включая запросы на сервер.

Чтобы использовать AbortController, необходимо создать экземпляр с таким же именем и передать его в качестве параметра запроса. Для этого используется метод signal.

Создадим экземпляр и объявим его сигнал:

const controller = new AbortController();
const signal = controller.signal;

А затем передадим его в параметры запроса:

axios.get('/user', { signal })
.then((response) => {
  console.log(response.data);
})

А также добавим функцию уведомления об отмене:

if (axios.isCancel(error)) {
    console.log('Запрос отменен', error.message);
}

Чтобы отменить запрос, необходимо вызвать метод abort() на экземпляре. Добавим его в конец кода:

controller.abort();

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

Оптимизация производительности при использования Axios

В этой главе мы рассмотрим несколько советов по оптимизации производительности при использовании Axios в вашем проекте:

  1. Кэширование запросов

    Часто возникает ситуация, когда мы отправляем один и тот же запрос несколько раз в течение короткого времени. В этом случае, мы можем использовать кэширование запросов, чтобы избежать отправки одного и того же запроса несколько раз. Для этого мы можем использовать библиотеку axios-cache-adapter или написать свой кэширующий обработчик.

  1. Минимизация числа запросов

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

  1. Отмена запросов

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

  1. Отложенная загрузка данных

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

  1. Использование интерсепторов

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

  1. Использование мемоизации

    Возможно использовать библиотеки мемоизации, такие как React.memo или useMemo, чтобы избежать повторного рендеринга компонентов, которые не изменились. Это может сократить число запросов к серверу и повысить производительность.

Заключение

Axios — это библиотека для отправки HTTP-запросов, которая может быть использована с React. В этой статье мы рассмотрели основы использования Axios и привели несколько примеров его

Понравилась статья? Поделить с друзьями:
  • Re8 exe системная ошибка emp dll
  • Re7 exe неустранимая ошибка приложения параметр задан неверно
  • Re7 exe неустранимая ошибка приложения как исправить
  • Re7 exe неустранимая ошибка приложения 0x887a0005
  • Re7 exe неустранимая ошибка приложения 0x80070057