Асинхронные трансформации в Zod возникают там, где проверка и преобразование данных не могут быть выполнены синхронно. Типичные причины: обращение к базе данных, запросы к внешним API, чтение файлов, проверка уникальности значений или сложная бизнес-валидация, требующая дополнительных вычислений.
Zod изначально проектировался как синхронная библиотека валидации схем, однако в экосистеме JavaScript практически любой прикладной сценарий валидации рано или поздно упирается в асинхронные операции. Для этого предусмотрены отдельные механизмы:
parseAsyncsafeParseAsyncrefinetransformsuperRefinez.promise(...)Асинхронность в Zod не является «режимом по умолчанию». Она включается явно, через использование соответствующих API.
Любая схема Zod может быть использована асинхронно, если внутри неё присутствует хотя бы одна асинхронная операция.
Основное правило:
parse() — только синхронные схемыparseAsync() — допускает асинхронные трансформации и
проверкиПри попытке использовать parse() на асинхронной схеме
возникает ошибка выполнения.
const schema = z.string().refine(async (val) => {
return val.startsWith("user_");
});
await schema.parseAsync("user_123");
Метод refine поддерживает асинхронные функции, что
позволяет подключать внешние источники данных.
const userSchema = z.string().refine(async (username) => {
const exists = await db.users.findUnique({
where: { username }
});
return !exists;
}, {
message: "Пользователь уже существует"
});
Особенность:
false трактуется как ошибка валидацииsuperRefine используется для сложных сценариев, когда
необходимо добавлять несколько ошибок или выполнять комбинированные
проверки.
const schema = z.object({
email: z.string().email(),
password: z.string().min(8)
}).superRefine(async (data, ctx) => {
const banned = await checkBannedEmailDomain(data.email);
if (banned) {
ctx.addIssue({
path: ["email"],
message: "Домен запрещён"
});
}
const leaked = await checkPasswordLeak(data.password);
if (leaked) {
ctx.addIssue({
path: ["password"],
message: "Пароль скомпрометирован"
});
}
});
Ключевой момент:
ctx.addIssue позволяет накопить несколько ошибокtransform позволяет изменять входные данные после
валидации. При использовании async возвращается Promise
результата.
const schema = z.string().email().transform(async (email) => {
const user = await fetchUserProfile(email);
return {
email,
name: user.name,
avatar: user.avatarUrl
};
});
Результат:
Особенность:
Асинхронные трансформации часто комбинируются в цепочки.
const schema = z.string()
.transform(async (val) => val.trim())
.transform(async (val) => val.toLowerCase())
.transform(async (val) => {
const normalized = await normalizeUsername(val);
return normalized;
});
Каждый этап:
parseAsyncАсинхронная версия безопасного парсинга возвращает структуру результата без выбрасывания ошибок.
const result = await schema.safeParseAsync(input);
if (!result.success) {
console.log(result.error.issues);
} else {
console.log(result.data);
}
Структура:
success: true → данные валидныsuccess: false → массив ошибокЭто основной механизм интеграции с UI и API слоями.
Zod поддерживает прямую работу с Promise-значениями.
const schema = z.promise(
z.string().transform(async (val) => {
return val.toUpperCase();
})
);
const result = await schema.parseAsync(Promise.resolve("hello"));
Особенность:
Асинхронная обработка в Zod проходит несколько фаз:
Preprocessing
z.preprocess)Validation
Async refine
Async transform
Final output
Важно:
Хотя preprocess обычно синхронный, он может возвращать
Promise через parseAsync.
const schema = z.preprocess(async (val) => {
const cleaned = await sanitize(val);
return cleaned;
}, z.string());
Это позволяет:
Асинхронные проверки в Zod не всегда выполняются параллельно. Поведение зависит от структуры схемы:
superRefine → могут быть
параллелизованы вручнуюОптимизация часто выглядит так:
superRefine(async (data, ctx) => {
const [a, b] = await Promise.all([
checkA(data),
checkB(data)
]);
if (!a) ctx.addIssue({ path: ["a"], message: "Ошибка A" });
if (!b) ctx.addIssue({ path: ["b"], message: "Ошибка B" });
});
TypeScript корректно выводит тип результата даже при использовании async transform.
const schema = z.string().transform(async (val) => {
return { value: val, length: val.length };
});
type Result = z.infer<typeof schema>;
// Promise<{ value: string; length: number }>
Важно:
Awaited<> для
извлеченияАсинхронные ошибки могут возникать в двух формах:
false в refine.refine(async (val) => {
throw new Error("внешний сервис недоступен");
});
Поведение:
Часто используется комбинированный подход:
const schema = z.string()
.refine(async (val) => await existsInDb(val))
.transform(async (val) => {
return await loadFullEntity(val);
});
Логика:
Асинхронные трансформации в Zod имеют архитектурные ограничения:
parse() для async схемПо этой причине сложные сценарии часто выносятся в слой сервисов, а Zod используется как слой валидации и нормализации данных.
Асинхронные трансформации в Zod формируют модель, где схема становится не только валидатором, но и оркестратором данных:
Zod в этом контексте выступает как декларативный слой обработки данных, объединяющий синхронную строгость и асинхронную гибкость JavaScript-экосистемы.