Как стать автором
Обновить

Комментарии 20

Спасибо, Amedomary!

Спасибо, Александр!

private static _processErrorMessages(messages: string[]): string[] {
  const resultMessages: string[] = [];
  messages.forEach((message) =>
    resultMessages.push(message.replace('  ', ' ').trim()),
  );
  return resultMessages;
}
static async getErrors(error: any): Promise<string[]> {
  let errorMessages: string[];

  if (axios.isAxiosError(error)) {
    const serviceName = this._getServiceName(
      error.config?.baseURL,
      error.config?.url,
    );

    if (error?.response) {
      errorMessages = await this._getResponseErrors(
        error.response,
        error.message,
        serviceName,
      );
      } else if (error?.request) {
        errorMessages = this._getRequestErrors(serviceName, error.code);
      } else {
        errorMessages = [
          `Не удалось отправить запрос к сервису ${serviceName}: ${error.message}`,
        ];
      }
    } else {
      errorMessages = this._handleNotAxiosError(error);
    }

   return this._processErrorMessages(errorMessages);
}

скопировал ваш код. Тут где-то ошибка.

_processErrorMessages - возвращает массив строк string[].

Далее вы возращаете этот массив в getErrors():

 return this._processErrorMessages(errorMessages);

Тип возвращаемого значения функции getErrors() Promise<string[]>

static async getErrors(error: any): Promise<string[]>

Но фактический возвращается не Promise<string[]>, а массив строк string[].

ALapinskas, спасибо за комментарий! Т.к. функция getErrors ассинхронная, то она всегда возвращает промис. Промис можно вернуть явно через Promise.resolve. Если ассинхронная функция возвращает тип отличный от промис, то этот тип автоматически оборачивается в промис. Вот здесь можно посмотреть эту информацию: https://learn.javascript.ru/async-await

Зачем в тс использовать "статические классы"? Просто интересно. Почему не модули или неймспейсы например?

Meonsou, спасибо за вопрос! В статье говорится, что данный алгоритм можно реализовать не только в ООП стиле, но и в функциональном. Классы в js не дают никакой новой функциональности, которую бы нельзя было бы реализовать с помощью функций и прототипного наследования. Т.е. это синтаксический сахар, который делает код более коротким и читаемым. Здесь можно ознакомиться с этим более детально https://learn.javascript.ru/class. Модули в JavaScript — это способ организации кода, который позволяет разделять его на отдельные файлы и управлять зависимостями между ними, а так же chunk-ами при сборке проекта. Модули могут экспортировать и импортировать функции, объекты или значения. Таким образом, и в классе и в модулях есть возможность управлять приватностью полей. Модули могут иметь преимущество перед классами, при разделении кода на файлы и использовании ленивой загрузки. Например, если в классе 3 метода, но нужен только 1, загрузятся все методы. В модульном подходе можно загрузить только код 1 метода, если использовать разделение на файлы и lazy loading. В тоже время, утилитарные функции бывает удобно располагать в классе, а не экспортировать по отдельности, т.к. при написании кода нужно помнить только имя класса, а дальше IDE подскажет, какие там есть методы. Например, DateUtils может содержать методы format, startOfDay, dateGreaterThan и т.д. и вместо нескольких названий нам нужно помнить только одно. С помощью модулей тоже можно добиться подобного поведения, но, с моей точки зрения, классы делают это более наглядно.

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

По поводу namespace. Этот подход уже несколько устарел, т.к. в модулях есть изоляция, ленивая загрузка, поддержке современных стандартов и т.д.

Неймспейсы предлагаются не как замена модулям, а как замена "статическим классам". В модулях. В этом плане они ни чуть не устарели.

export namespace Utils {
  export function test() {}
}
export class Utils {
  static function test() {}
}

Статические классы имеют такой же объем кода, что и namespace и сборщики (webpack и прочие) поддерживают их из коробки. Meonsou, в чем с Вашей точки зрения, преимущества namespaces перед статическими методами и почему Вы не рекомендуете использовать их?

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

Что касается моего личного мнения, я не люблю использовать классы как пространства имён потому что это добавляет когнитивной сложности коду. Когда я вижу слово class я ожидаю увидеть там класс, а не пространство имён. По этой же логике например предпочитаю function declaration вместо стрелок. Но эту вкусовщину я не навязываю никому.

Классы в js не дают никакой новой функциональности, которую бы нельзя было бы реализовать с помощью функций и прототипного наследования. Т.е. это синтаксический сахар, который делает код более коротким и читаемым. Здесь можно ознакомиться с этим более детально https://learn.javascript.ru/class

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

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

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

Модули могут иметь преимущество перед классами, при разделении кода на файлы и использовании ленивой загрузки

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

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

Вот тут как раз и всплывает неточность в понимании классов. Класс - это способ разбиения кода по смыслу. Поэтому, даже для примера вам не может потребоваться часть класса для загрузки. Понятно, что вы имеете в виду класс с одними статическими методами. Но когда для примера вы начинаете резать его на самостоятельные программные единицы сразу хочется закричать: "Что за ужас тут творится!!!". Т.к. вы работаете на ТС как это "Например" типизировать можно, что бы было удобно?

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

Тут опять возникает вопрос организации кода по смыслу. Например, что мешает вам сделать что-то типа: const MyLib = { ... } as const; export default MyLib IDE будет так же подсказывать, а сама конструкция не требует оборачивания в class и использования static

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

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

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

1.1 Vitally_js, если правильно поняла, то вместо предложения "Т.е. это синтаксический сахар, который делает код более коротким и читаемым.", Вы считаете более правильным формулировку без "Т.е." : "Это синтаксический сахар, который делает код более коротким и читаемым." Верно? Если сочинительный союз "то есть" Вам не нравится, то, на мой взгляд, это дело вкуса.

1.2 Если быть совсем точным, то, все же, есть 3 отличия функциональности js класса, от js функций. В указанном ранее источнике перечислены 3 отличия https://learn.javascript.ru/class#ne-prosto-sintaksicheskiy-sahar. Первое - функция, созданная с помощью class, помечена специальным внутренним свойством [[IsClassConstructor]]: true и конструктор класса, в отличие от функции-констуктора, не может быть вызван без оператора new. Доступа к свойству [[IsClassConstructor]] снаружи нет и изменить его не получится. Поправьте, если я ошибаюсь и есть какой-то хак как это сделать. Если говорить, про запрет вызова функции-конструктора без new, то это можно сделать следующим образом:

// Функция-конструктор
function UserFn() {
  if(!new.target){
      throw new Error('Функция UserFn должна вызываться с оператором new');
  }
}

// с "new":
new UserFn(); // UserFn {}

// без "new":
UserFn(); // Uncaught Error: Функция UserFn должна вызываться с оператором new

// Класс
class UserClass{
   constructor(name) {
    this.name = name;
  }
}

// с "new":
new UserClass(); // UserClass {name: undefined}

// без "new":
UserClass(); // Uncaught TypeError: Class constructor UserClass cannot be invoked without 'new'

Второе отличие класса от функции-конструктора - методы класса являются неперечислимыми. Однако, можно сделать, чтобы методы функции-конструктора тоже были неперечисляемыми:

// Kласс
class UserClass{
   constructor(name) {
    this.name = name;
  }
  printName(){
      console.log(this.name);
  }
}

const Ann = new UserClass('Ann');
Ann.printName(); // Ann
for(prop in Ann){
    console.log(`Property ${prop} has value ${Ann[prop]}`);
} // Property name has value Ann


// Функция-конструктор
function UserFn(name) {
  this.name = name;
}
const John = new UserFn('John');
Object.defineProperty(John, "printName", {
    value: function() {
      console.log(this.name);
    }
});
John.printName(); // John

for(prop in John){
    console.log(`Property ${prop} has value ${John[prop]}`);
} // Property name has value John

Третье - классы всегда используют use strict. Однако, строгий режим можно включить вручную:

"use strict";
function UserFn(name) {
  this.name = name;
}
/* ... */

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

2.

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

2.1 В js есть возможность управлять приватностью, если имя поля начать с #: https://learn.javascript.ru/private-protected-properties-methods#privatnoe-svoystvo-waterlimit
Цитата из указанного источника: "На уровне языка # является специальным символом, который означает, что поле приватное. Мы не можем получить к нему доступ извне или из наследуемых классов.". В ts есть модификатор доступа к полям private.

3.

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

3.1 Под разделением кода класса на файла я имела ввиду концепцию partual классов, которые есть, например, есть в c#, но отсутствует в js и ts. Разделение на файлы было упомянуто в контексте статических методов в ответе на комментарий @meonsou. Соглашусь с meonsou, что если все методы класса являются статическими, то класс используется, скорее, как пространство имен. В проектах, написанных с помощью namespace подхода, действительно, логичнее использовать общее пространство имен, чем статический класс. Однако, с проектами с namespace я работала последний раз лет 10 назад и, как раз, их потом переписывали на модули, чтобы использовать webpack и все преимущества модулей. Кстати, при совсем большом желании можно в js написать код класса в нескольких файлах:

// myClass.js
class MyClass {
  prop1 = 1;
  someMethod2(){
    /*...*/
  }
}

// defineObsoleteMyClassMethods.js
import { MyClass } from './myClass.js';

function defineObsoleteMyClassMethods(){
 Object.defineProperty(MyClass, 'someMethod1', {
    value: function () {
      console.log('someMethod1');
    },
  });
}

// test.js
import { MyClass } from './myClass.js';
import { defineObsoleteMyClassMethods } from "./obsoleteMyClassMethods";

defineObsoleteMyClassMethods();
MyClass['someMethod1'](); // 'someMethod1'

Vitally_js, в namespace подходе, насколько помню, можно использовать классы. А так же наследование классов и прочий их функционал. Значит, высказывание: "Просто потому, что при любом разбиении на файлы вы будете использовать модули.", - не совсем верное. Поправьте, если это не так.

4.

Вот тут как раз и всплывает неточность в понимании классов. Класс - это способ разбиения кода по смыслу. Поэтому, даже для примера вам не может потребоваться часть класса для загрузки. Понятно, что вы имеете в виду класс с одними статическими методами. Но когда для примера вы начинаете резать его на самостоятельные программные единицы сразу хочется закричать: "Что за ужас тут творится!!!". Т.к. вы работаете на ТС как это "Например" типизировать можно, что бы было удобно?

4.1 Vitally_js, попрошу Вас придерживаться делового стиля общения.

Классы в js b ts могут содержать и статические и нестатические свойства и методы. Выше я привела пример из своей практики: была необходимость в новой версии пакета скрыть из подсказок синтаксиса старые методы, но не удалять их, чтобы при обновлении пакета не делать breaking change. Это было сделано с помощью Object.defineProperty. В одном файле с кодом класса или нет - уже не помню, но это и не важно.

5.

Тут опять возникает вопрос организации кода по смыслу. Например, что мешает вам сделать что-то типа: const MyLib = { ... } as const; export default MyLib IDE будет так же подсказывать, а сама конструкция не требует оборачивания в class и использования static

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

6

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

6.1 Vitally_js, я имела ввиду модули+классы и модули+функции+константы и прочее вместо класса.

Вам не нравится, то, на мой взгляд, это дело вкуса.

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

Если быть совсем точным, то, все же, есть 3 отличия функциональности js класса ...

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

В js есть возможность управлять приватностью, если имя поля начать с #

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

Значит, высказывание: "Просто потому, что при любом разбиении на файлы вы будете использовать модули.", - не совсем верное. Поправьте, если это не так.

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

// test.js

import { MyClass } from './myClass.js';

import { defineObsoleteMyClassMethods } from "./obsoleteMyClassMethods";

Вот эта вот штука говорит, что код разделен на два модуля: ./myClass.js, ./obsoleteMyClassMethods

Поэтому вы как раз наглядно показали то о чем я написал.

попрошу Вас придерживаться делового стиля общения

А где я вышел за рамки? Все в цитатах и кавычках. Все по деловому.

Классы в js b ts могут содержать и статические и нестатические свойства и методы. Выше я привела пример из своей практики: была необходимость в новой версии пакета скрыть из подсказок синтаксиса старые методы, но не удалять их, чтобы при обновлении пакета не делать breaking change. Это было сделано с помощью Object.defineProperty.

Честно говоря, не понял идею. Если убирать из подсказок, то нужно править декларацию типов. Или вы про чистый js? Там да, можно скрыть.

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

Так в данном случае и интересно, чем вкус определяется? Какой смысл в этом огромном количестве private, static, class если можно просто внутри модуля использовать function? Вы использовали категорию "удобно" и объяснили чем именно. Но ровно тоже самое можно сделать и без большего количества обслуживающего кода. Еще и нейминг упростится, не нужны будут _

6.1 Vitally_js, я имела ввиду модули+классы и модули+функции+константы и прочее вместо класса.

Я это понял, что думали о чем-то подобном. Поэтому это и было замечание по форме.

IIkhambek, да можно ловить все ошибки в interceptors. Не совсем поняла проще в сравнении с чем? Если имеется ввиду примеры кода с отловом ошибок в catch axios запроса, то они только для демонстрации того, как можно получить указанные ошибки.

а что если все ошибки ловить в interceptors ах? так разве не проще?

IIkhambek, да можно ловить все ошибки в interceptors. Не совсем поняла проще в сравнении с чем? Если имеется ввиду примеры кода с отловом ошибок в catch axios запроса, то они только для демонстрации того, как можно получить указанные ошибки.

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

Kacetal, спасибо за вопрос! Писать общий перехватчик ошибок в конфиге React Query можно с 3-ей, а возможно, и с более ранней версии. Выглядит это следующим образом:

// queryClient.ts
// 4я версия @tanstack/react-query
export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      onError: async (error: unknown) => {
        const validationErrors = await ErrorUtils.getErrors(error);

        /* Вывести сообщения об ошибке. Например, в виде красного 
        всплывающего окна */
        /*...*/
      },
    },
    mutations: {
      onError: async (error: unknown) => {
        const validationErrors = await ErrorUtils.getErrors(error);

        /* Вывести сообщения об ошибке. Например, в виде красного 
        всплывающего окна */
        /*...*/
      },
    },
  },
});
// queryClient.ts
// 5я версия @tanstack/react-query
export const queryClient = new QueryClient({
  // Для перехвата ошибок в queries
  queryCache: new QueryCache({
    onError: async (error: unknown) => {
      const validationErrors = await ErrorUtils.getErrors(error);

        /* Вывести сообщения об ошибке. Например, в виде красного 
        всплывающего окна */
        /*...*/
    },
  }),
  defaultOptions: {
    mutations: {
      onError: async (error: unknown) => {
        const validationErrors = await ErrorUtils.getErrors(error);

        /* Вывести сообщения об ошибке. Например, в виде красного 
        всплывающего окна */
        /*...*/
      },
    },
  },
});

Далее подключаем query client через QueryClientProvider в приложении:

// App.ts
const App = () => {
  return (
    <Body>
      {/*...*/}
        <QueryClientProvider client={queryClient}>
          {/*...*/}
        </QueryClientProvider>
     {/*...*/}
    </Body>
  );
};

export default App;

Создаем инстанс axios сервиса:

export const exampleServiceApi = axios.create({
  // Задаем baseURL и прочие настройки
  /*...*/
});

Пишем, например, GET запрос:

// T - тип модели, которую присылает бек
export function getExampleItem(id: string) : Promise<T> {
  return statementQueryServiceApi
    // T - тип модели, которую присылает бек
    .get<T>(`/example-url/${id}`, {
      /* description - кастомное свойство AxiosRequestConfig. 
      Оно не обязательно. В статье выше указано, 
      как его добавить в AxiosRequestConfig для определения метода,
      в котором произошла ошибка запроса*/
      description: 'получения информации о элементе',
    })
    .then(({ data }) => data);
}

Далее, можно сделать либо query либо mutation:

// T - тип модели, которую присылает бек
export function useExample(id?: string): UseQueryResult<T, Error> {
  return useQuery({
    queryKey: ['exampleItem', id],
    queryFn: () => getExampleItem(id!),
    enabled: !!id,
  });
}

В react-компоненте или другом хуке данный хук вызывается следующим образом:

const { data: example } = useExample(id);

Можно создать мутацию:

// 5я версия @tanstack/react-query
const {
    mutate: getExampleItemFn,
    isPending: isLoadingExample, // в 4ой версии isLoading вместо isPending
    data: example,
  } = useMutation({
    mutationFn: (id: string) => getExampleItem(id)
  });

В коде можно, вызвать эту мутацию, например, по клику на кнопку:

// react-компонент Button, который имеет индикацию загрузки
<Button loading={isLoadingExample} onClick={() => getExampleItemFn(10)} />

Зарегистрируйтесь на Хабре, чтобы оставить комментарий