parseAsync и safeParseAsync

Асинхронная модель валидации

Валидация данных в Zod изначально синхронна: схемы описывают структуру, типы и ограничения, которые могут быть проверены немедленно. Однако в реальных сценариях часть проверок требует асинхронных операций — запросов к базе данных, внешним API, файловой системе. Для таких случаев используются parseAsync и safeParseAsync, которые расширяют стандартный механизм выполнения схем, переводя его в промис-ориентированную модель.

Асинхронные схемы возникают при использовании:

  • refine с асинхронной функцией
  • superRefine с async
  • transform с асинхронной логикой
  • проверок уникальности через БД
  • внешних HTTP-запросов в процессе валидации

parseAsync: строгая асинхронная валидация с исключениями

Метод parseAsync выполняет полную проверку входных данных согласно схеме и возвращает промис. В случае ошибки выполнение прерывается с выбросом исключения.

Сигнатура поведения

  • Возвращает Promise<T>
  • При успехе: резолвит валидированное значение
  • При ошибке: выбрасывает ZodError

Базовый пример

import { z } from "zod";

const schema = z.object({
  email: z.string().email(),
  id: z.string().refine(async (val) => {
    const exists = await fakeDatabaseCheck(val);
    return exists;
  }, {
    message: "ID не найден"
  })
});

async function run() {
  const data = await schema.parseAsync({
    email: "test@mail.com",
    id: "123"
  });
}

В этом примере refine содержит асинхронную операцию, поэтому использование parse невозможно — требуется parseAsync.


Механика выполнения parseAsync

При вызове происходит последовательность:

  1. Синхронная проверка типов и структуры
  2. Запуск всех асинхронных refinement-операций
  3. Ожидание завершения всех промисов
  4. Агрегация результата
  5. Возврат финального значения или ошибка

Важно, что даже если только один слой схемы содержит асинхронную логику, вся цепочка автоматически становится асинхронной.


Поведение при ошибках

При первом же несоответствии данных или возврате false из refine происходит генерация ZodError. Исключение содержит:

  • путь к полю (path)
  • тип ошибки (code)
  • сообщение (message)
  • дополнительные данные (expected, received)
try {
  await schema.parseAsync(input);
} catch (err) {
  console.log(err.errors);
}

Особенность parseAsync: выполнение прерывается на этапе первой критической ошибки, если схема не использует superRefine с накоплением ошибок.


safeParseAsync: безопасная модель без исключений

Метод safeParseAsync возвращает результат в виде объекта, исключая необходимость обработки исключений через try/catch.

Структура результата

{
  success: true,
  data: T
}

или

{
  success: false,
  error: ZodError
}

Базовый пример safeParseAsync

const result = await schema.safeParseAsync(input);

if (result.success) {
  console.log(result.data);
} else {
  console.log(result.error.issues);
}

Основное отличие — управление потоком выполнения остаётся у вызывающего кода, без исключений.


Сравнение parseAsync и safeParseAsync

parseAsync

  • Использует исключения
  • Подходит для сервисного слоя и строгих контрактов
  • Упрощает цепочки await
  • Требует try/catch при ошибках

safeParseAsync

  • Не использует исключения
  • Возвращает структурированный результат
  • Удобен для UI-слоя и API-валидации
  • Позволяет явно обрабатывать ошибки

Асинхронные refine и их влияние

Асинхронность в Zod активируется только при наличии async-операций внутри схемы.

const usernameSchema = z.string().refine(async (val) => {
  const taken = await checkUsername(val);
  return !taken;
}, {
  message: "Имя занято"
});

Без parseAsync или safeParseAsync такой код не выполнится корректно.


superRefine в асинхронном режиме

superRefine позволяет добавлять множественные ошибки и сложную логику проверки.

const schema = z.object({
  password: z.string()
}).superRefine(async (data, ctx) => {
  const breached = await checkPasswordLeak(data.password);

  if (breached) {
    ctx.addIssue({
      code: "custom",
      message: "Пароль скомпрометирован"
    });
  }
});

При использовании superRefine асинхронность распространяется на всю схему.


Особенности выполнения цепочек трансформаций

Асинхронные трансформации изменяют тип результата после валидации:

const schema = z.string().transform(async (val) => {
  return await hashValue(val);
});

parseAsync в этом случае возвращает уже преобразованный результат.


Комбинация sync и async логики

Zod допускает смешанные схемы:

  • структура может быть синхронной
  • проверки — асинхронными
  • трансформации — частично async

В таких случаях весь pipeline автоматически становится промисом.


Производительность и поведение промисов

Асинхронная валидация добавляет накладные расходы:

  • каждая async-ветка создаёт промис
  • все ветки агрегируются через Promise.all
  • глубокие схемы увеличивают латентность

При большом количестве refine с внешними запросами возникает эффект «цепной задержки», особенно если нет кеширования.


Ошибки проектирования асинхронной валидации

Типичные проблемы:

  1. Использование parse вместо parseAsync
  2. Смешивание sync и async refine без понимания влияния на всю схему
  3. Отсутствие ограничения числа внешних запросов
  4. Дублирование проверок в нескольких слоях схемы

Поведение вложенных схем

Если хотя бы один вложенный объект содержит async-логику, вся верхнеуровневая схема становится асинхронной:

const userSchema = z.object({
  profile: z.object({
    username: z.string().refine(async (val) => {
      return await check(val);
    })
  })
});

Даже при вызове верхнего уровня требуется parseAsync.


Обработка массивов в async-схемах

Асинхронные проверки в массивах выполняются параллельно:

const schema = z.array(
  z.string().refine(async (val) => {
    return await checkItem(val);
  })
);

Каждый элемент проходит отдельную async-валидацию, агрегируемую в общий результат.


Контракт возврата safeParseAsync в сложных схемах

При сложных вложенных структурах safeParseAsync сохраняет полную иерархию ошибок:

{
  success: false,
  error: {
    issues: [
      {
        path: ["profile", "username"],
        message: "Имя занято"
      }
    ]
  }
}

Это позволяет точно локализовать источник ошибки без исключений.


Применение в серверной архитектуре

Асинхронная валидация используется в:

  • проверке уникальности пользователей
  • валидации токенов доступа
  • проверке прав через внешние сервисы
  • загрузке и проверке файлов

parseAsync чаще применяется в бизнес-логике, где ошибка должна прерывать выполнение. safeParseAsync используется на границах системы — API, формы, UI.


Итоговая модель поведения

  • parseAsync — строгая валидация с исключениями и прерыванием потока
  • safeParseAsync — функциональная валидация с явным результатом
  • асинхронность активируется автоматически при наличии async-логики внутри схем
  • вся схема становится промис-ориентированной при любом async-узле
  • ошибки структурируются через ZodError или объект результата