Проблемы с асинхронной валидацией

Природа асинхронной валидации в Yup

Библиотека Yup поддерживает асинхронные проверки через пользовательские тесты, возвращающие Promise. Такая модель позволяет интегрировать серверные проверки, запросы к API и любые операции, требующие времени выполнения вне синхронного контекста.

Асинхронная логика в Yup реализуется через test, где функция может возвращать промис. Результат промиса определяет успешность или провал проверки. На уровне API это выглядит как расширение синхронной схемы, однако поведение внутри цепочки валидации существенно отличается.

const schema = yup.object({
  email: yup.string().test(
    'unique-email',
    'Email уже используется',
    async (value) => {
      const res = await fetch(`/api/check-email?value=${value}`);
      const data = await res.json();
      return data.unique;
    }
  )
});

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


Отсутствие встроенной отмены запросов

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

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

Сценарий:

  1. Вводится значение a@example.com
  2. Запускается асинхронная проверка (запрос №1)
  3. Значение меняется на b@example.com
  4. Запускается новый запрос (№2)
  5. Ответ №2 приходит быстрее
  6. Ответ №1 приходит позже и перезаписывает состояние

Так формируется классическая проблема гонки состояний (race condition).


Гонка асинхронных результатов

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

  • проверка логина
  • валидация промокода
  • проверка доступности имени пользователя
  • запрос правил бизнес-логики с сервера

Поскольку Yup не хранит контекст “актуального запроса”, результат последнего завершившегося промиса считается валидным, даже если он относится к устаревшему состоянию формы.

Типичная ошибка проявляется в виде “мерцающих” ошибок: поле сначала становится валидным, затем снова невалидным без изменения значения.


Отсутствие встроенного дебаунса

Асинхронная валидация в Yup запускается каждый раз при вызове validate или validateAt. В отличие от UI-уровня, библиотека не управляет частотой вызовов.

При вводе текста в реальном времени это приводит к множеству параллельных запросов:

  • каждая клавиша инициирует новый Promise
  • предыдущие проверки не прерываются
  • нагрузка на сервер растёт линейно с частотой ввода

Дебаунс обычно реализуется вне Yup, на уровне формы или обработчика событий, однако отсутствие встроенного механизма приводит к фрагментации логики.


Проблемы интеграции с форменными библиотеками

При использовании Yup в связке с библиотеками управления формами, например Formik или React Hook Form, асинхронные ошибки начинают проявляться через сложные цепочки обновлений состояния.

Основные проблемы:

  • повторная валидация при каждом setFieldValue
  • параллельные вызовы validateField
  • конфликт локального и серверного состояния ошибок
  • задержка синхронизации isValid

В результате состояние формы перестаёт быть детерминированным в момент активного ввода.


Несогласованность между validate и isValid

Методы validate, validateAt и isValid имеют различную семантику обработки асинхронности.

  • validate возвращает полную структуру ошибок или undefined
  • validateAt проверяет отдельное поле
  • isValid возвращает булево значение

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


Перезапись результатов и устаревшие ошибки

Асинхронные тесты могут завершаться в произвольном порядке. Это создаёт ситуацию, когда:

  • старый запрос возвращает “ошибка”
  • новый запрос уже установил “успех”
  • финальное состояние формы получает некорректное значение

Yup не хранит идентификаторы запросов, поэтому нет механизма сопоставления результата с конкретной версией значения поля.


Сложности при валидации зависимых полей

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

Это создаёт каскад:

  • изменение поля A
  • пересчёт поля B
  • асинхронная проверка B
  • изменение поля C как зависимого от B

В условиях высокой частоты изменений формируется лавинообразный поток промисов.


Отсутствие централизованного управления состоянием запросов

Асинхронная валидация в Yup не имеет встроенного слоя управления состоянием запросов. Это означает:

  • нет очереди запросов
  • нет дедупликации
  • нет отмены предыдущих операций
  • нет приоритизации последних значений

Каждый тест выполняется изолированно, что упрощает API, но усложняет практическое поведение в UI.


Кэширование как частичное решение

Частичная стабилизация поведения достигается через ручное кэширование результатов проверки.

Например, проверка уникальности может использовать локальный кеш:

  • значение уже проверялось → возвращается сохранённый результат
  • новое значение → выполняется запрос

Однако такой подход имеет ограничения:

  • риск устаревших данных
  • необходимость инвалидировать кеш
  • сложность синхронизации между компонентами

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


Проблемы с параллельной валидацией формы

При вызове полной валидации схемы (schema.validate) происходит одновременный запуск всех асинхронных тестов. Если несколько полей содержат async-логику, формируется набор параллельных промисов.

Сложность возникает в следующих аспектах:

  • отсутствие контроля порядка завершения
  • невозможность частичного обновления результата
  • блокировка финального результата до завершения всех тестов

При больших формах это может приводить к значительным задержкам.


Ошибки при повторной валидации

Повторные вызовы валидации часто происходят при каждом изменении состояния формы. При асинхронной логике это формирует цепочки незавершённых промисов.

Типичные последствия:

  • утечка времени выполнения (long-running promises)
  • накопление незавершённых проверок
  • рост сетевой нагрузки
  • нестабильное UI-состояние

Особенно заметно при медленных API или нестабильном соединении.


Невозможность прерывания цепочки тестов

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

Это создаёт фундаментальное ограничение архитектуры: логика валидации отделена от жизненного цикла UI-компонента.


Проблемы масштабирования асинхронной логики

При увеличении количества асинхронных проверок в схеме возникают следующие эффекты:

  • рост количества одновременных промисов
  • увеличение вероятности race condition
  • усложнение отладки
  • нелинейный рост времени валидации

Особенно критично это проявляется в сложных формах с зависимыми полями и серверной валидацией.


Итоговая структура проблемного пространства

Асинхронная валидация в Yup формирует набор системных ограничений:

  • отсутствие отмены запросов
  • отсутствие управления версиями значений
  • отсутствие встроенного дебаунса
  • параллельное выполнение без координации
  • слабая синхронизация с UI-состоянием
  • сложность работы с зависимыми полями

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