Асинхронные валидаторы в class-validator используются в
сценариях, где проверка свойства требует обращения к внешним источникам:
базе данных, HTTP-сервисам, кэшу или файловой системе. В отличие от
синхронных проверок, асинхронные ограничения возвращают
Promise, что влияет на весь жизненный цикл валидации
объекта.
Основная особенность заключается в том, что результат валидации становится отложенным во времени, а система должна корректно агрегировать как успешные проверки, так и ошибки, возникающие в ходе выполнения асинхронных операций.
Асинхронный валидатор в class-validator определяется
через функцию, возвращающую Promise<boolean> или
значение, приводимое к промису. В типичном виде кастомное ограничение
реализует интерфейс ValidatorConstraintInterface.
Ключевым моментом является то, что возвращаемое значение не содержит ошибок напрямую — оно лишь сигнализирует о результате проверки:
true — значение прошло валидациюfalse — значение не соответствует правилуPromise.reject() или выброшенное исключение —
критическая ошибка выполнения проверкиВнутренний механизм class-validator агрегирует все
валидаторы через Promise.all. Это означает, что:
Такая модель делает невозможным ранний выход при первой ошибке, если
явно не используется стратегия остановки выполнения
(stopAtFirstError), которая влияет только на уровень
обработки результатов, но не отменяет уже запущенные промисы.
Асинхронный валидатор может сигнализировать о проблеме двумя основными способами:
falseНаиболее безопасный вариант. Ошибка фиксируется как обычное нарушение ограничения.
throw new Error()Исключение перехватывается системой и преобразуется в
ValidationError. При этом текст ошибки становится частью
constraints.
Promise.reject()Аналогично throw, но через механизм промисов.
Используется реже, но приводит к тому же результату.
Важно, что class-validator не различает семантически
throw и reject — оба пути приводят к
формированию ошибки уровня ограничения.
Поведение функций верхнего уровня определяет способ обработки ошибок.
Функция validate() возвращает массив
ValidationError[]. При асинхронных валидаторах:
Структура ошибки включает:
propertyconstraintschildrenvalueФункция validateOrReject() изменяет модель
обработки:
Promise.reject(ValidationError[])voidАсинхронные валидаторы в этом режиме не прерывают выполнение заранее — они также дожидаются завершения всех промисов перед формированием rejection.
Асинхронная ошибка не отличается по структуре от синхронной. Отличие заключается только в источнике:
{
property: 'email',
value: 'test@example.com',
constraints: {
isEmailTaken: 'Email already exists'
}
}
Если внутри асинхронного валидатора происходит исключение без явного
текста, class-validator формирует сообщение на основе
Error.message. При отсутствии сообщения используется
стандартная строка.
Когда одно свойство имеет несколько асинхронных ограничений, все они
выполняются параллельно. Итоговая структура constraints
содержит все нарушенные правила.
Пример поведения:
Все три проверки могут завершиться независимо, и каждая добавит свою
запись в constraints, если возвращает false
или выбрасывает ошибку.
Асинхронные валидаторы часто зависят от HTTP-запросов или запросов к БД. Это формирует несколько важных технических аспектов:
При использовании ORM или HTTP-клиентов важно возвращать именно
Promise, а не смешивать синхронные побочные эффекты с
асинхронным результатом.
class-validator не управляет таймаутами асинхронных
операций. Это означает, что:
Типичная проблема возникает при проверке уникальности:
Такая ситуация не решается средствами class-validator и
требует транзакционной логики или уникальных индексов на уровне хранения
данных.
Асинхронные валидаторы часто используют сервисы, внедряемые через DI
(например, TypeDI или NestJS интеграции). В
этом случае важно учитывать:
Нарушение этих условий приводит к неконсистентным результатам валидации при параллельных запросах.
Типичная реализация асинхронного ограничения включает проверку внешнего источника данных:
@ValidatorConstraint({ async: true })
class IsEmailUniqueConstraint implements ValidatorConstraintInterface {
async validate(email: string): Promise<boolean> {
const user = await userRepository.findOne({ where: { email } });
return !user;
}
defaultMessage(): string {
return 'Email уже используется';
}
}
В этом случае:
validate возвращает
Promise<boolean>false автоматически превращается в ошибкуdefaultMessage применяется при отсутствии кастомного
сообщенияАсинхронный валидатор может выбрасывать исключение для передачи более сложного контекста ошибки:
async validate(email: string): Promise<boolean> {
const response = await api.checkEmail(email);
if (response.status === 429) {
throw new Error('Сервис проверки перегружен');
}
return response.available;
}
Такой подход приводит к тому, что ошибка становится частью
constraints, но теряет структурированность, если не
обрабатывать её явно через ValidationError.
При работе с асинхронными валидаторами часто требуется унифицировать формат ошибок. Это связано с тем, что источники ошибок могут быть разнородными:
Структура ValidationError.constraints позволяет
агрегировать их в единый формат, однако без дополнительной логики
возможна потеря семантики ошибок.
Распространённый подход — приведение всех ошибок к строковому
описанию внутри defaultMessage или через обёртку над
валидатором, чтобы исключить неоднозначность источников отказа.
При использовании validateNested или декораторов
@ValidateNested() асинхронные валидаторы внутри вложенных
объектов выполняются по той же модели Promise-агрегации. Это
означает:
childrenЭто поведение критично при валидации сложных DTO, где каждая ветка может иметь собственные внешние зависимости.