DevGang
Авторизоваться

Получение данных с помощью Axios

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

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

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

Настройка Axios

Чтобы настроить Axios, мы вызываем модуль axios и применяем метод create. В настоящее время мы предоставляем только два параметра: baseURL (основной путь к серверу) и headers по умолчанию для клиента.

export const client = (() => {
  return axios.create({
    baseURL: process.env.REACT_BASE_URL,
    headers: {
      Accept: "application/json, text/plain, */*",
    },
  });
})();

Обратите внимание, что функция client является выражением функции, вызываемым немедленно, то есть она вызывается сразу после определения. Крайне важно создавать экземпляр запроса axios только один раз, когда загружается модуль axios. Это соответствует шаблону Singleton, обычно используемому в JavaScript.  

const request = async (options: AxiosRequestConfig) => {
  const onSuccess = (response: AxiosResponse) => {
    const { data } = response;
    return data;
  };

  const onError = function (error: AxiosError) {
    return Promise.reject({
      message: error.message,
      code: error.code,
      response: error.response,
    });
  };

  return client(options).then(onSuccess).catch(onError);
};

export default request;

В запросе у нас есть две разные функции: onSuccess и onError. Каждая из них возвращает axios экземпляр  Response/Error. Используя структуру обещания, мы вызываем then, когда запрос успешен, и catch, когда запрос не удался. Этот подход дает два весомых преимущества:

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

Axios перехватчики

Перехватчик запросов:

client.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    const accessToken = localStorage.getItem(STORAGE_TOKEN.ACCESS_TOKEN);
    if (accessToken) {
      config.headers.Authorization = `Bearer ${accessToken}`;
    }
    return config;
  },
  (error: AxiosError) => {
    return Promise.reject(error);
  },
);

Прежде чем клиент отправит запрос на сервер, его можно изменить. В следующем примере мы получаем accessToken из LocalStorage и устанавливаем его в глобальной конфигурации, которую получаем в качестве параметра для нашей функции. После добавления токена Bearer мы возвращаем измененный конфиг. Этот шаг необходим, поскольку если это ограниченный пользовательский интерфейс (панель управления, админ-панели и т. д.), все запросы требуют авторизации. Дополнительно отметим, что в случае ошибки мы можем вернуть promise.reject с произошедшей ошибкой.

Перехватчик ответов:

client.interceptors.response.use(
  (res: AxiosResponse) => {
    return res; // Simply return the response
  },
  async (err) => {
    const status = error.response ? error.response.status : null;

    if (status === 401) {
      try {
        const refreshTokenFromStorage = localStorage.getItem(
          STORAGE_TOKEN.REFRESH_TOKEN
        );
        const { accessToken, refreshToken } = await AuthService.refresh(
          refreshTokenFromStorage
        );

        LocalStorageService.setTokens(accessToken, refreshToken);
        client.defaults.headers.common.Authorization = `Bearer ${accessToken}`;

        return await client(originalConfig);
      } catch (error: AxiosError) {
        return Promise.reject(error);
      }
    }

    if (status === 403 && err.response.data) {
      return Promise.reject(err.response.data);
    }

    return Promise.reject(err);
  }
);

Во втором перехватчике мы уже модифицируем полученные от сервера данные. Если сервер отвечает правильно, мы просто возвращаем ответ. Однако если возникает ошибка, мы предпринимаем конкретные действия. Во-первых, нам необходимо идентифицировать ошибку, отправленную сервером, обычно это делается путем проверки стандартизированных статусов. В следующем коде мы проверяем статус, чтобы определить соответствующее действие. Если это ошибка 401, мы обновляем токены с помощью RefreshToken, срок действия которого более длительный. При ошибке 403 мы выдаем Forbidden Error. Это пример того, как мы можем глобально обрабатывать каждый запрос. Имейте в виду, что в зависимости от спецификаций вашего проекта вы можете обрабатывать ошибки любым желаемым способом. Если ваш сервер отправляет определенные ошибки, вы можете обрабатывать их на основе других параметров.

Структура проекта

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

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

Конфигурационный файл

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

export const ProductEndpoints = {
  getMyProducts: () => "product/me",
  getProduct: (id: string | number) => `product/${id}`,
  getProductsFromOrder: (orderToken: string) => `/product/order-products?token=${orderToken}`,
};

Служебный файл

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

export default class ProductService {
  public static readonly getMyProducts = ({
    offset,
    limit,
    textFilter,
  }: ProductQueryParams): Promise<Product[]> => {
    return request({
      url: ProductEndpoints.getMyProducts(),
      method: AxiosMethod.GET,
      params: {
        limit,
        offset,
      },
    });
  };

  public static readonly getProduct = (
    id: string | number,
  ): Promise<Product> => {
    return request({
      url: ProductEndpoints.getProduct(id),
      method: AxiosMethod.GET,
    });
  };

  public static readonly getProductsFromOrder = (
    token: string,
  ): Promise<Product[]> => {
    return request({
      url: ProductEndpoints.getProductsFromOrder(token),
      method: AxiosMethod.GET,
    });
  };
}

Мы выполнили первоначальную настройку Axios, настроили перехватчики и установили структуру папок для вызовов API.

Источник

#NodeJS
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу