Валидация данных в Zod изначально синхронна, однако современная разработка часто связана с источниками данных, которые требуют ожидания результата: HTTP-запросы, чтение файлов, обращение к базам данных. Для таких сценариев используется асинхронный режим выполнения схем.
Ключевое отличие заключается в использовании методов:
parseAsyncsafeParseAsyncz.promise(...)Синхронные методы parse и safeParse не
поддерживают промисы и завершаются сразу, тогда как асинхронные аналоги
позволяют обрабатывать данные после разрешения Promise.
z.promise как обёртка над асинхронным значениемКонструкция z.promise(schema) описывает тип, который
является промисом, возвращающим валидируемое значение.
import { z } from "zod";
const userSchema = z.object({
id: z.number(),
name: z.string(),
});
const userPromiseSchema = z.promise(userSchema);
Такое описание означает:
PromiseuserSchemaАсинхронная обработка требует явного использования соответствующих методов:
const result = await userPromiseSchema.parseAsync(fetchUser());
или безопасный вариант:
const result = await userPromiseSchema.safeParseAsync(fetchUser());
Разница:
parseAsync → выбрасывает ZodError при
неудачеsafeParseAsync → возвращает объект
{ success, data, error }Важно понимать, что Zod не валидирует промис до его выполнения. Проверка происходит только после резолва:
const schema = z.promise(
z.object({
id: z.number(),
})
);
await schema.parseAsync(Promise.resolve({ id: "not a number" }));
Ошибка будет сгенерирована после разрешения промиса, а не в момент его создания.
Типичный сценарий — проверка ответа HTTP-запроса:
const apiResponseSchema = z.promise(
z.object({
id: z.number(),
username: z.string(),
})
);
async function getUser() {
return fetch("/api/user")
.then(res => res.json());
}
const user = await apiResponseSchema.parseAsync(getUser());
Здесь происходит цепочка:
fetch возвращает
Promise<Response>.json() возвращает Promise<any>Zod позволяет добавлять асинхронные проверки без оборачивания в
z.promise.
const schema = z.object({
email: z.string().email(),
}).refine(async (data) => {
const exists = await checkEmailExists(data.email);
return !exists;
}, {
message: "Email уже используется",
});
Такая проверка требует использования parseAsync, иначе
выполнение завершится некорректно.
Для более точного контроля используется superRefine,
позволяющий добавлять ошибки вручную:
const schema = z.object({
password: z.string(),
}).superRefine(async (data, ctx) => {
const isWeak = await checkPasswordStrength(data.password);
if (isWeak) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Слабый пароль",
path: ["password"],
});
}
});
Механика:
ctxZodErrorТрансформации могут выполняться после валидации, включая асинхронные случаи.
const schema = z.object({
userId: z.string(),
}).transform(async (data) => {
const user = await fetchUserById(data.userId);
return {
...data,
user,
};
});
Важно:
parseAsyncPromiseПри использовании промисов процесс валидации проходит несколько этапов:
Promiserefine / superRefinetransformЛюбая ошибка на любом этапе прерывает цепочку.
z.promise часто комбинируется с базовыми схемами для
строгого описания API:
const postSchema = z.object({
id: z.number(),
title: z.string(),
});
const postsSchema = z.promise(z.array(postSchema));
Здесь описывается:
При асинхронной валидации ошибки сохраняют структуру:
try {
await schema.parseAsync(data);
} catch (err) {
if (err instanceof z.ZodError) {
console.log(err.issues);
}
}
Особенности:
awaitpath)| Характеристика | parse | parseAsync |
|---|---|---|
| Поддержка Promise | нет | да |
| async refine | нет | да |
| superRefine async | нет | да |
| transform async | нет | да |
Асинхронные схемы требуют дисциплины: любая операция, возвращающая
Promise, автоматически переводит схему в async-режим.
const responseSchema = z.promise(
z.object({
data: z.array(z.number()),
})
);
const schema = z.object({
username: z.string(),
}).refine(async (data) => {
return !(await isBanned(data.username));
});
const schema = z.object({
productId: z.string(),
}).transform(async (data) => {
const product = await loadProduct(data.productId);
return product;
});
Асинхронная модель накладывает ряд ограничений:
parse при наличии
async-операцийawait внутри схем требуют
parseAsyncЭто делает поведение предсказуемым, но требует явного контроля над асинхронностью.
Сложные структуры часто комбинируют несколько уровней асинхронности:
const commentSchema = z.object({
text: z.string(),
});
const postSchema = z.object({
id: z.number(),
comments: z.promise(z.array(commentSchema)),
});
В этом случае:
Асинхронные схемы удобно включаются в пайплайны:
const schema = z.promise(
z.string()
.transform(async (value) => value.trim())
.refine(async (value) => value.length > 0)
);
Здесь данные проходят:
Каждый шаг может зависеть от внешних ресурсов.