Валидация JSON-схем в Ajv традиционно синхронна, однако современный
стек разработки требует поддержки асинхронных проверок: запросов к базе
данных, обращений к внешним API, чтения распределённых кэшей. Для этого
в библиотеке реализована полноценная модель работы через
Promise, позволяющая встраивать асинхронную логику прямо в
процесс валидации.
Асинхронность в Ajv строится вокруг двух ключевых механизмов:
"$async": truecompileAsync$asyncФлаг "$async": true включает режим, при котором
валидатор всегда возвращает Promise. Любая ошибка приводит
к отклонению промиса с объектом Ajv.ValidationError.
Пример схемы:
const schema = {
$async: true,
type: "object",
properties: {
userId: { type: "string" }
},
required: ["userId"]
};
Компиляция такой схемы:
import Ajv from "ajv";
const ajv = new Ajv();
const validate = ajv.compile(schema);
validate({ userId: "123" })
.then(data => {
console.log("Данные валидны", data);
})
.catch(err => {
console.log("Ошибка валидации", err);
});
Особенность заключается в том, что даже если внутри схемы нет
асинхронных операций, результат всё равно оборачивается в
Promise, обеспечивая единый интерфейс обработки.
compileAsyncПри использовании динамически загружаемых схем или схем с
потенциальной асинхронной подготовкой применяется
compileAsync.
const validate = await ajv.compileAsync(schema);
Этот метод возвращает Promise, который резолвится
функцией-валидатором. Внутренне Ajv анализирует наличие
$async и асинхронных зависимостей.
Пример с обработкой:
try {
const validate = await ajv.compileAsync(schema);
const result = await validate(payload);
console.log(result);
} catch (e) {
console.error(e);
}
compileAsync особенно полезен в системах, где схемы
подгружаются из внешних источников и требуют предварительной
нормализации.
Одной из наиболее мощных возможностей Ajv является расширение через
addKeyword. Поддержка async позволяет включать
проверку, например, существования пользователя в базе данных.
ajv.addKeyword({
keyword: "userExists",
async: true,
type: "string",
validate: async function checkUser(schema, data) {
const user = await db.findUserById(data);
return Boolean(user);
}
});
Использование в схеме:
const schema = {
$async: true,
type: "string",
userExists: true
};
При валидации:
await validate("user-123");
Если пользователь не найден, промис будет отклонён.
Асинхронные ключевые слова выполняются последовательно в рамках одной схемы, что важно учитывать при проектировании сложных цепочек проверок.
Ajv позволяет определять пользовательские форматы через
addFormat, включая асинхронные проверки.
ajv.addFormat("emailExists", {
async: true,
validate: async (email) => {
const exists = await emailService.check(email);
return exists;
}
});
Схема:
const schema = {
$async: true,
type: "string",
format: "emailExists"
};
Форматы выполняются на уровне типовой проверки и интегрируются в общий pipeline валидации.
При использовании Promise-ориентированной модели Ajv
формирует структурированное исключение ValidationError,
содержащее:
errors — массив ошибокdataPath — путь к некорректному значениюschemaPath — путь к правилу схемыparams — дополнительные параметры контекстаПример обработки:
try {
await validate(data);
} catch (err) {
for (const error of err.errors) {
console.log(error.instancePath, error.message);
}
}
Важно, что отклонение промиса происходит только при
invalid результате. Успешная валидация возвращает исходные
данные или undefined в зависимости от конфигурации.
При проектировании сложных систем валидации часто требуется запуск
нескольких независимых проверок. Хотя Ajv сам управляет
последовательностью асинхронных шагов внутри схемы, внешняя
параллелизация возможна через Promise.all.
const validators = [
ajv.compileAsync(schemaUser),
ajv.compileAsync(schemaProfile),
ajv.compileAsync(schemaPermissions)
];
const [v1, v2, v3] = await Promise.all(validators);
await Promise.all([
v1(userData),
v2(profileData),
v3(permissionData)
]);
Такой подход снижает латентность при валидации независимых сущностей.
В системах обработки событий (например, очереди сообщений или стримы) асинхронные валидаторы применяются как промежуточные фильтры.
async function processMessage(msg) {
try {
await validate(msg.payload);
await handle(msg.payload);
} catch (e) {
await sendToDeadLetterQueue(msg);
}
}
Ajv в этом контексте выступает как первый слой защиты данных, предотвращающий попадание неконсистентных структур в бизнес-логику.
Асинхронные схемы требуют более строгого контроля состояния:
Ajv не выполняет автоматическую дедупликацию асинхронных операций внутри пользовательских расширений, что требует аккуратного проектирования внешних зависимостей.
Асинхронная модель Ajv естественно интегрируется в middleware-подход.
app.post("/api", async (req, res) => {
try {
await validate(req.body);
res.send({ ok: true });
} catch (e) {
res.status(400).send(e.errors);
}
});
В таких сценариях валидатор становится частью цепочки обработки запроса, где каждая стадия может быть асинхронной.
Ajv допускает смешанные схемы, где часть проверок выполняется
синхронно, а часть — через Promise.
const schema = {
$async: true,
type: "object",
properties: {
id: { type: "string" },
email: { format: "emailExists" }
},
required: ["id", "email"]
};
Синхронные проверки выполняются немедленно, асинхронные становятся частью цепочки промисов, формируя единый поток валидации.
Использование Promise добавляет накладные расходы,
однако Ajv оптимизирует выполнение за счёт:
Основная стоимость возникает не в Ajv, а в пользовательских асинхронных операциях (HTTP, DB, I/O).
Если асинхронная функция выбрасывает исключение, оно интерпретируется как ошибка валидации:
validate: async () => {
throw new Error("DB failure");
}
В этом случае Ajv преобразует исключение в
ValidationError, сохраняя контекст схемы и ключевого
слова.
Такой механизм обеспечивает единообразную обработку как бизнес-ошибок, так и технических сбоев.