Если ошибка это ожидаемое поведение

Замена выброса исключений уведомлениями

Время на прочтение
10 мин

Количество просмотров 17K

Предлагаю вашему вниманию перевод статьи «Replace Throw With Notification» Мартина Фаулера. Примеры адаптированы под .NET.

Если мы валидируем данные, обычно мы не должны использовать исключения, чтобы известить о валидационных ошибках. Здесь я опишу как отрефакторить такой код с использованием паттерна «Уведомление» («Notification»).

Недавно я смотрел на код, который делал базовую валидацию входящих JSON сообщений. Это выглядело примерно так…

public void Сheck()
{
   if (Date == null) throw new ArgumentNullException("Дата не указана");
   DateTime parsedDate;
   try {
     parsedDate = DateTime.Parse(Date);
   }
   catch (FormatException e) {
     throw new ArgumentException("Дата указана в неизвестном формате", e);
   }
   if (parsedDate < DateTime.Now) throw new ArgumentException("Дата не может быть раньше сегодняшней");
   if (NumberOfSeats == null) throw new ArgumentException("Количество мест не указано");
   if (NumberOfSeats < 1) throw new ArgumentException("Количество мест должно быть положительным числом");
 }

Это общий подход к реализации валидации. Запускается серия проверок для некоторых данных (в примере выше валидируются два параметра). Если какая-либо проверка не проходит, то кидается исключение с сообщением об ошибке.

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

Если ошибка — это ожидаемое поведение, то мы не должны использовать исключения

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

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

private void ValidateNumberOfSeats(Notification note)
{
  if (numberOfSeats < 1) note.addError("Количество мест должно быть положительным числом");
  // другие проверки, как проверка выше
}

Затем мы можем просто вызвать метод Notification.hasErrors(), чтобы отреагировать на ошибки. Другие методы Notification могут предоставить больше деталей об ошибках.

if (numberOfSeats < 1) throw new ArgumentException(«Количество мест должно быть положительным числом»);

  if (numberOfSeats < 1) note.addError("Количество мест должно быть положительным числом");
  return note;

Когда использовать этот рефакторинг

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

Хорошее правило использования исключений можно встретить в книге «Pragmatic Programmers»:

Мы верим, что исключения редко должны использоваться, как часть нормального потока программы: исключения должны быть зарезервированы для неожиданный ситуаций. Представьте, что необработанное исключение завершит вашу программу и спросите себя: «Будет ли этот код всё ещё работать, если я уберу все обработчики исключений?» Если ответ «нет», то, возможно, исключения использовались в составе нормального потока программы.

— Дэйв Томас и Энди Хант

Важный вывод, который нужно сделать из этого — решение применять ли исключения для конкретной задачи зависит от контекста. Чтение файла, который не существует может являться исключительной ситуацией, а может и не являться. Если мы пытаемся прочитать файл по хорошо известному пути, например, /etc/hosts в Unix, то мы можем предположить, что файл должен быть здесь, поэтому выбрасывание исключения имеет смысл. С другой стороны, если мы читаем файл по пути, переданному пользователем, через командную строку, то мы должны ожидать, что, вероятно, файла здесь нет и использовать другой механизм взаимодействия с неисключительной по своей природе ошибкой.

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

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

Стартовая точка

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

  JsonConvert.DeserializeObject<BookingRequest>(json);

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

  class BookingRequest
  {
    public int? NumberOfSeats { get; set; }
    public string Date { get; set; }
  }

Валидация уже была показана выше.

public void Сheck()
{
   if (Date == null) throw new ArgumentNullException("Дата не указана");
   DateTime parsedDate;
   try {
     parsedDate = DateTime.Parse(Date);
   }
   catch (FormatException e) {
     throw new ArgumentException("Дата указана в неизвестном формате", e);
   }
   if (parsedDate < DateTime.Now) throw new ArgumentException("Дата не может быть раньше сегодняшней");
   if (NumberOfSeats == null) throw new ArgumentException("Количество мест не указано");
   if (NumberOfSeats < 1) throw new ArgumentException("Количество мест должно быть положительным числом");
 }

Создание нотификации

Чтобы использовать нотификации мы должны создать объект Notification. Нотификация может быть достаточно простой, временами просто List.

  var notification = new List<string>();
  if (NumberOfSeats < 5) notification.add("Количество мест должно быть не менее 5");
  // ещё проверки

  // затем…
  if (notification.Any()) // обработка ошибок

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

public class Notification
{
	private List<String> errors = new List<string>();

	public void AddError(string message)
	{
		errors.Add(message);
	}

	public bool HasErrors
	{
		get { return errors.Any(); }
	}
}

Используя специальный класс мы делаем наши намерения более очевидными — читателю не нужно создавать мысленную карту между идей и её реализацией.

Разделяем метод Check

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

Используя способ «Выделение метода», выносим тело функции Check в функцию Validation.

public void Сheck()
{
	Validation();
}

public void Validation()
{
	if (Date == null) throw new ArgumentNullException("Дата не указана");
	DateTime parsedDate;
	try
	{
		parsedDate = DateTime.Parse(Date);
	}
	catch (FormatException e)
	{
		throw new ArgumentException("Дата указана в неизвестном формате", e);
	}
	if (parsedDate < DateTime.Now) throw new ArgumentException("Дата не может быть раньше сегодняшней");
	if (NumberOfSeats == null) throw new ArgumentException("Количество мест не указано");
	if (NumberOfSeats < 1) throw new ArgumentException("Количество мест должно быть положительным числом");
}

Затем расширяем метод Validation с созданием Notification и его возвращением из функции.

public Notification Validation()
{
    	var notification = new Notification();
        //...
        return notification;
}

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

public void Сheck()
{
	var notification = Validation();

	if (notification.HasErrors)
		throw new ArgumentException(notification.ErrorMessage);
}

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

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

Разделение изначального метода позволило нам отделить валидацию от реакции на её результаты

Перед тем как мы продолжим, следует сказать несколько слов о сообщениях об ошибке. Когда мы делаем рефакторинг, важно избежать изменений в наблюдаемом поведении. Данное правило ведёт нас к вопросу о том какое поведение является наблюдаемым. Очевидно, что выброс исключения — это то, что внешняя программа будет наблюдать, но в какой степени они заботятся о сообщении об ошибке? Notification будет собирать множество сообщений об ошибках и объединять их в одно, например таким образом.

public string ErrorMessage
{
	get { return string.Join(", ", errors); }
}

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

public string ErrorMessage
{
	get { return errors[0]; }
}

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

Валидация числа

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

public Notification Validation()
{
        var notification = new Notification();
        if (Date == null) notification.AddError("Дата не указана");
        //...
}

Очевидная замена, но плохая, так как ломает код. Если мы передадим null в качестве аргумента для Date, то мы добавим ошибку в объект Notification, код продолжит выполняться и при разборе получим NullReferenceException в методе DateTime.Parse. Это не то, что мы хотим получить.

Неочевидное, но более эффективное, что нужно сделать в этом случае, это идти с конца метода.

public Notification Validation()
{
       //...
       if (NumberOfSeats < 1) notification.AddError("Количество мест должно быть положительным числом");
}

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

public Notification Validation()
{
       //...
       if (NumberOfSeats == null) notification.AddError("Количество мест не указано");
       else if (NumberOfSeats < 1) notification.AddError("Количество мест должно быть положительным числом");
}

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

public Notification Validation()
{
       //...
       ValidateNumberOfSeats(notification);
}

private void  ValidateNumberOfSeats(Notification notification)
{
       if (NumberOfSeats == null) notification.AddError("Количество мест не указано");
       else if (NumberOfSeats < 1) notification.AddError("Количество мест должно быть положительным числом");
}

Когда мы смотрим на выделенную валидацию для числа, она выглядит не очень естественно. Использование if-then-else блоков для валидации может легко привести к чрезмерно вложенному коду. Более предпочтительно использовать линейный код, который обрывается, если не может идти далее, что мы можем реализовать с использованием защитного условия.

private void ValidateNumberOfSeats(Notification notification)
{
	if (NumberOfSeats == null)
	{
		notification.AddError("Количество мест не указано");
		return;
	}

	if (NumberOfSeats < 1) notification.AddError("Количество мест должно быть положительным числом");
}

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

Валидация даты

Начнем с выноса проверок для даты в отдельный метод.

public Notification Validation()
{
       ValidateDate(notification);
       ValidateNumberOfSeats(notification);
}

Затем, как и в случае с числом, начнём заменять исключения с конца метода.

private void ValidateNumberOfSeats(Notification notification)
{
	//...
	if (parsedDate < DateTime.Now) notification.AddError("Дата не может быть раньше сегодняшней");
}

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

Добавим в метод AddError параметр Exception и укажем ему значение по умолчанию null.

public void AddError(string message, Exception exc = null)
{
	errors.Add(message);
}

Это значит мы принимаем исключение, но игнорируем его. Чтобы поместить его куда-либо мы должны изменить тип ошибки внутри класса Notification с string на более сложный объект. Создадим класс Error внутри Notification.

private class Error
{
	public string Message { get; set; }
	public Exception Exception { get; set; }

	public Error(string message, Exception exception)
	{
		Message = message;
		Exception = exception;
	}
}

Теперь у нас есть класс и нам осталось изменить Notification, чтобы он использовал его.

//...
private List<Error> errors = new List<Error>();

public void AddError(string message)
{
	errors.Add(new Error(message, null));
}

public void AddError(string message, Exception exception = null)
{
	errors.Add(new Error(message, exception));
}

//...
public string ErrorMessage
{
	get { return string.Join(", ", errors.Select(e => e.Message)); }
}

С новым уведомлением на месте, теперь мы можем внести изменения в запрос бронирования.

private void ValidateDate(Notification notification)
{
	if (Date == null) throw new ArgumentNullException("Дата не указана");
	DateTime parsedDate;
	try
	{
		parsedDate = DateTime.Parse(Date);
	}
	catch (FormatException e)
	{
		notification.AddError("Дата указана в неизвестном формате", e);
		return;
	}
	if (parsedDate < DateTime.Now) notification.AddError("Дата не может быть раньше сегодняшней");
}

И последнее изменение достаточно простое.

private void ValidateDate(Notification notification)
{
	if (Date == null) notification.AddError("Дата не указана");
	DateTime parsedDate;
	try
	{
		parsedDate = DateTime.Parse(Date);
	}
	catch (FormatException e)
	{
		notification.AddError("Дата указана в неизвестном формате", e);
		return;
	}
	if (parsedDate < DateTime.Now) notification.AddError("Дата не может быть раньше сегодняшней");
}

Заключение

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

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

I noticed some unexpected behavior (unexpected relative to my personal expectations), and I’m wondering if something if there is a bug in the JVM or if perhaps this is a fringe case where I don’t understand some of the details of what exactly is supposed to happen. Suppose we had the following code in a main method by itself:

int i;
int count = 0;
for(i=0; i < Integer.MAX_VALUE; i+=2){
  count++;
}
System.out.println(i++);

A naive expectation would be that this would print Integer.MAX_VALUE-1, the largest even representable int. However, I believe integer arithmetic is supposed to «rollover» in Java, so adding 1 to Integer.MAX_VALUE should result in Integer.MIN_VALUE. Since Integer.MIN_VALUE is still less than Integer.MAX_VALUE, the loop would keep iterating through the negative even ints. Eventually it would get back to 0, and this process should repeat as an infinite loop.

When I actually run this code, I get non-deterministic results. The result that gets printed tends to be on the order of half a million, but the exact value varies. So not only is the loop terminating when I believe it should be an infinite loop, but it seems to terminate randomly. What’s going on?

My guess is that this is either a bug in the JVM, or there is a lot of funky optimization going on that makes this expected behavior. Which is it?

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

int i;
int count = 0;
for(i=0; i < Integer.MAX_VALUE; i+=2){
  count++;
}
System.out.println(i++);

Наивное ожидание будет состоять в том, что это напечатает Integer.MAX_VALUE-1, самый большой даже представимый int. Тем не менее, я считаю, что целочисленная арифметика должна «опрокинуться» на Java, поэтому добавление 1 к Integer.MAX_VALUE должно привести к Integer.MIN_VALUE. Поскольку Integer.MIN_VALUE все еще меньше Integer.MAX_VALUE, цикл будет продолжать итерацию через отрицательные четные int. В конце концов, он вернется к 0, и этот процесс должен повторяться как бесконечный цикл.

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

Моя догадка заключается в том, что это либо ошибка в JVM, либо есть много фанковой оптимизации, которая делает это ожидаемым поведением. Что это?

#php #null-coalescing-operator

#php #null-coalescing-operator

Вопрос:

Я наткнулся на оператор if, использующий PHPs null coalesce operator, который ведет себя не так, как «ожидалось». Рассматриваемый код выглядит примерно так:

 if ($foo['bar'] ?? false || $foo['baz'] ?? false) { /* ... */ }
  

меняем его на

 if (($foo['bar'] ?? false) || ($foo['baz'] ?? false)) { /* ... */ }
  

решает это.

Я провел быстрый тест в своем терминале:

 root@docker:/application# php -v
PHP 7.2.11-2 ubuntu18.04.1 deb.sury.org 1 (cli) (built: Oct 15 2018 11:40:35) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.11-2 ubuntu18.04.1 deb.sury.org 1, Copyright (c) 1999-2018, by Zend Technologies
    with Xdebug v2.6.1, Copyright (c) 2002-2018, by Derick Rethans
root@docker:/application# php -a
Interactive mode enabled

php > $test = ['foo' => 'bar'];
php > var_dump($test['baz'] ?? null); // as expected
php shell code:1:
NULL
php > var_dump(($test['baz'] ?? null)); // as expected
php shell code:1:
NULL
php > var_dump($test['baz'] ?? null || $test['foobar'] ?? null); // as expected, but there's a Notice
PHP Notice:  Undefined index: foobar in php shell code on line 1
PHP Stack trace:
PHP   1. {main}() php shell code:0
php shell code:1:
bool(false)
php > var_dump(($test['baz'] ?? null) || ($test['foobar'] ?? null)); // as expected
php shell code:1:
bool(false)

  

Теперь, что, я думаю, происходит в тесте № 3, так это то, что он выполняется как

 $test['baz'] ?? (null || $test['foobar']) ?? null
  

итак, если $test['baz'] вычисляется значение unset (что, очевидно, и происходит), выполняется следующий null || $test['foobar'] get, что приводит к $test['foobar'] отправке уведомления.

Мой вопрос: Это ожидаемое поведение оператора null coalesce в PHP? Я вроде ожидал, что он будет привязываться сильнее, чем, например, оператор || (or).
С другой стороны, в RFC (https://wiki.php.net/rfc/isset_ternary), есть явный пример:

 var_dump(0 || 2 ?? 3 ? 4 : 5); // ((0 || 2) ?? 3) ? 4 : 5 => int(4)
  

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

Что вы думаете? Должно ли об этом сообщаться как об ошибке? Я знаю, что это не «правильный» вопрос, однако, поскольку я не смог найти отчет об ошибке / обсуждение / ветку об этом, я подумал, что должен быть ресурс, документирующий это.
Если вы / моды не согласны, я снова удалю вопрос.

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

1. Если RFC подтверждает поведение, почему это должно быть ошибкой?

2. Разве это не просто приоритет оператора . || имеет более высокий приоритет, чем ?? .

3. @PatrickQ Это точно не подтверждает поведение, но у IMO есть похожий случай. Вот почему я спросил.

4. Я не думаю, что ответ необходим. Это скорее случай RTM.

5. @Barmar ИМО, по этой причине многие вопросы здесь должны исчезнуть. Но опять же, я не согласен со всей новой философией контента ради контента.

Ответ №1:

Это ожидаемое поведение из-за приоритета оператора

|| имеет более высокий приоритет, чем ?? , поэтому ваше исходное утверждение обрабатывается как

 if ($foo['bar'] ?? (false || $foo['baz']) ?? false)
  

Пусть вас не уловит это несколько неожиданное взаимодействие.

На днях я писал GET-запрос к конечной точке и в ходе тестирования получил много 400 ошибок. 400 ошибок связаны с ошибками клиента, в основном ошибками, вызванными лицом, сделавшим запрос. Вот некоторые распространенные ошибки 400:
— 400 неверный запрос
— 401 неавторизованный
— 404 не найден
— 418 Я чайник
( Хорошо, может быть, не столько этот, но это настоящий код ошибки)

Теперь это было абсолютно нормально, я мог столкнуться с этими ошибками. Но чего я не ожидал, так это того, что мой API Hook (в моем случае React-query) запускал метод onSuccess, когда возвращался ответ API. По сути, мой вызов завершился с ошибкой 400, но моя ловушка управления API думала, что это удалось. Очень странно.

Сначала я подозревал нечестную игру в React-query, что как-то 400 ошибок были закодированы как успехи, хотя мне это казалось безумием.
Но, покопавшись, я обнаружил, что на самом деле это «ошибка» fetch API. И, как выясняется, это вовсе не ошибка, это ожидаемое поведение. Итак, как fetch обрабатывает статусы ошибок HTTP?

Что такое успех?

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

1. Мы отправили запрос
2. Сервер ответил
3. Мы авторизовались на этот запрос
4. Наш запрос выполнен
5. Все работает как надо, мы получаем статус 200 в ОК 🥳

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

Если вы думаете, как я, в том смысле, что успех в основном синонимичен статусу 200, то это немного сложная концепция, чтобы смириться. Мы можем вернуть ошибку 500, но все равно быть технически успешным? Получить говорит «да».

Итак, как мы обрабатываем статусы ошибок с помощью выборки?

Давайте посмотрим на код.

export const getExample = async ({ token }) => {
    const response = await fetch("API_URL", {
        method: "GET",
        headers: {
            "Authorization": `Bearer ${token}`
        }
    });
    return response;
};

Эта getExample функция возвращает обещание, которое является ответом на простой запрос GET fetch API. Если бы мы использовали это обещание с помощью библиотеки управления API (такой как React-query / Apollo и т. Д.), Они бы сказали нам, что это успешный вызов, поскольку обещание ответа не было выполнено. Помните — Fetch не выполняет обещание только при сетевой ошибке, но не при статусе ошибки. Если мы хотим изменить это поведение, нам нужно вручную выполнить / отклонить обещание.

К счастью, это очень просто. Хотя fetch не нарушает обещания для нас, он устанавливает флаг ответа ok в значение false, если возвращаемый статус ошибки не равен 200–299. Подробнее о флаге response.Ok здесь — https://developer.mozilla.org/en-US/docs/Web/API/Response/ok

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

...
if (!response.ok) await Promise.reject(new Error(response.status.toString())); //Write your own error msg here
return response;

Резюме

API Success! == 200 OK
Fetch не заботится о статусе ошибки, пока получает ответ от сервера. Нам нужно вручную проверить и не выполнить обещание ответа, если код не такой, как мы хотим.
Или… мы могли бы просто использовать Axios, который автоматически делает это за нас.

Источники

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

Возможно, вам также будет интересно:

  • Если ошибка что нет стем
  • Если ошибка формула на английском
  • Если ошибка формула в макросе
  • Если ошибка ушла после сброса
  • Если ошибка только в рсв 1

  • Понравилась статья? Поделить с друзьями:
    0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии