Синхронность и асинхронность валидации в class-validator определяют, в каком режиме выполняются проверки значений свойств объектов: немедленно в рамках текущего потока исполнения или с ожиданием завершения внешних операций, возвращающих Promise. Эти два подхода используются совместно в одной модели данных, но имеют разные ограничения и поведение внутри пайплайна валидации.
В основе работы библиотеки лежит идея декларативного описания правил
через декораторы и последующего запуска механизма проверки, который
обходит свойства объекта и применяет к ним набор валидаторов. Каждый
валидатор может быть синхронным либо асинхронным, и это напрямую влияет
на общий результат выполнения функции validate.
Синхронные валидаторы выполняются мгновенно и возвращают результат в
виде boolean. Они не предполагают ожидания внешних ресурсов
и полностью работают в пределах текущего вызова функции.
Наиболее распространённый сценарий — проверка формата, диапазона значений, длины строк, соответствия регулярным выражениям и других детерминированных условий.
Примеры встроенных синхронных валидаторов:
@IsString()@IsNumber()@Length()@Matches()@IsBoolean()Пример использования:
import { IsString, Length, IsNumber } from 'class-validator';
class UserDto {
@IsString()
name;
@IsNumber()
age;
@IsString()
@Length(5, 20)
password;
}
При выполнении валидации:
import { validate } from 'class-validator';
const user = new UserDto();
user.name = 123;
user.age = 'old';
user.password = '123';
validate(user).then(errors => {
console.log(errors);
});
Все проверки выполняются последовательно в рамках одного цикла, без ожидания внешних процессов. Синхронные валидаторы оптимальны по производительности, поскольку не создают дополнительных промисов и не блокируют поток событий.
Особенность синхронных валидаторов заключается в том, что они должны возвращать результат немедленно. Любая попытка выполнить внутри них асинхронный код будет проигнорирована с точки зрения корректности пайплайна.
Асинхронные валидаторы используются в случаях, когда проверка требует
обращения к внешним источникам: базе данных, API, файловой системе или
другим сервисам. Они возвращают Promise<boolean> или
Promise<void> и интегрируются в общий процесс через
механизм await.
Для создания асинхронного кастомного валидатора используется
ValidatorConstraint с параметром
async: true.
Пример асинхронного валидатора:
import {
ValidatorConstraint,
ValidatorConstraintInterface,
} from 'class-validator';
@ValidatorConstraint({ async: true })
class IsEmailAlreadyExists implements ValidatorConstraintInterface {
async validate(email) {
const user = await database.findUserByEmail(email);
return !user;
}
defaultMessage() {
return 'Email already exists';
}
}
Использование в DTO:
import { Validate } from 'class-validator';
class RegisterDto {
@Validate(IsEmailAlreadyExists)
email;
}
При вызове validate() библиотека определяет наличие
асинхронных валидаторов и автоматически переключает выполнение в режим
работы с Promise. В результате возвращается
Promise<ValidationError[]>, даже если часть
валидаторов синхронная.
const errors = await validate(dto);
Если хотя бы один валидатор асинхронный, весь процесс становится асинхронным. Это важно учитывать при проектировании архитектуры, поскольку нельзя смешивать ожидание синхронного результата с предположением мгновенного возврата.
Асинхронные проверки выполняются параллельно на уровне внутренних Promise-цепочек, однако порядок завершения не гарантирует порядок обработки ошибок. Итоговый массив ошибок формируется после завершения всех проверок.
В реальных приложениях синхронные и асинхронные валидаторы часто используются совместно. Например:
import { IsString, Length, Validate } from 'class-validator';
class ProfileDto {
@IsString()
username;
@Length(6, 30)
password;
@Validate(IsEmailAlreadyExists)
email;
}
В этом случае:
username и password проверяются
синхронноemail проверяется асинхронно через обращение к базе
данныхРезультат всей валидации становится асинхронным, несмотря на наличие синхронных правил.
Механизм class-validator строится вокруг обхода метаданных,
сохранённых декораторами. Для каждого свойства формируется список
правил, которые выполняются через Promise.all или
аналогичный механизм объединения результатов.
Ключевые особенности:
awaitАсинхронные валидаторы вводят дополнительные накладные расходы:
Особенно критично использование асинхронных проверок внутри циклов массовой обработки объектов, где каждая сущность инициирует отдельный запрос.
Пример неэффективного подхода:
const users = arrayOfUsers.map(u => validate(u));
await Promise.all(users);
Если каждый валидатор обращается к базе данных, нагрузка возрастает линейно относительно количества объектов.
Синхронные валидаторы:
true или falseАсинхронные валидаторы:
Promise<boolean>Promise<void> с выбросом ошибки через
defaultMessageНарушение этого контракта приводит к непредсказуемому поведению системы валидации.
Асинхронные валидаторы часто применяются для внедрения зависимостей через DI-контейнеры (например, в NestJS), что позволяет инкапсулировать доступ к данным:
@ValidatorConstraint({ async: true })
class IsUniqueUsername {
constructor(private userService) {}
async validate(username) {
return !(await this.userService.exists(username));
}
}
Такой подход делает валидацию частью бизнес-логики, а не только синтаксической проверки входных данных.
Разделение валидаторов на синхронные и асинхронные влияет на структуру DTO и слой обработки запросов:
Чем больше асинхронных проверок присутствует в DTO, тем сильнее модель зависит от внешней среды и тем менее предсказуемо поведение валидации в условиях высокой нагрузки или нестабильной сети.