В Superstruct асинхронная валидация применяется в случаях, когда проверка значения требует обращения к внешним источникам данных или выполнения операций, результат которых невозможно получить синхронно. Типичный пример — проверка уникальности имени пользователя через API, валидация токена, обращение к базе данных или удалённому сервису.
Основная особенность Superstruct заключается в том, что библиотека изначально проектировалась как синхронный валидатор, но при этом предоставляет механизмы расширения, позволяющие безопасно интегрировать асинхронные проверки через пользовательские структуры и функции валидации.
Асинхронная логика в Superstruct чаще всего строится вокруг
пользовательских структур, созданных через define, а также
расширения поведения через refine, где допускается возврат
Promise.
import { define } fr om 'superstruct'
const UniqueUsername = define('UniqueUsername', async (value) => {
const response = await fetch(`/api/check-username?name=${value}`)
const data = await response.json()
return data.available === true
})
В этом примере структура возвращает true или
false, но фактически выполнение происходит асинхронно. Это
делает возможным подключение внешних сервисов без изменения общей
архитектуры валидации.
Стандартная функция assert в Superstruct работает
синхронно:
import { assert, string } from 'superstruct'
assert('hello', string())
Попытка напрямую использовать await внутри стандартных
структур невозможна. Поэтому асинхронная логика всегда выносится в
пользовательские структуры или в этапы предварительной обработки.
Ключевое ограничение заключается в том, что композиция структур сама по себе остаётся синхронной, а асинхронность становится внешним слоем.
Функция validate поддерживает асинхронный сценарий через
возвращаемый Promise.
import { validate, string } from 'superstruct'
async function runValidation(value) {
const [error, result] = await validate(value, string())
if (error) {
console.log('Ошибка валидации')
return
}
console.log('Корректное значение:', result)
}
При подключении пользовательских асинхронных структур
validate автоматически начинает возвращать промис, что
позволяет строить цепочки проверок.
refine используется для добавления дополнительной логики
проверки поверх базовой структуры.
import { string, refine } from 'superstruct'
const Email = refine(string(), 'Email', async (value) => {
const response = await fetch(`/api/check-email?email=${value}`)
const result = await response.json()
return result.valid
})
Такой подход позволяет комбинировать базовую типизацию (например, строка) с бизнес-логикой, вынесенной во внешний сервис.
Superstruct позволяет вкладывать структуры друг в друга, однако при использовании асинхронных проверок важно учитывать, что весь стек валидации становится асинхронным.
import { object, string } from 'superstruct'
const User = object({
name: string(),
email: Email
})
async function validateUser(data) {
const [error, value] = await validate(data, User)
if (error) {
throw error
}
return value
}
Если хотя бы одна вложенная структура использует асинхронную
проверку, результат всей валидации становится Promise.
Практически полезной считается модель, при которой базовая проверка выполняется синхронно, а внешние проверки — асинхронно.
import { string, size } from 'superstruct'
const UsernameBase = size(string(), 3, 20)
import { refine } from 'superstruct'
const Username = refine(UsernameBase, 'Username', async (value) => {
const res = await fetch(`/api/username/${value}`)
const data = await res.json()
return data.available
})
Такое разделение уменьшает количество внешних вызовов и ускоряет предварительную фильтрацию данных.
Ошибки в асинхронных структурах могут возникать как на уровне сети, так и на уровне логики проверки.
const SafeEmail = refine(string(), 'SafeEmail', async (value) => {
try {
const res = await fetch(`/api/email-check?email=${value}`)
if (!res.ok) {
return false
}
const data = await res.json()
return data.allowed
} catch (e) {
return false
}
})
Здесь важно учитывать, что возврат false трактуется как
провал валидации, а не как исключение.
При работе с массивами каждый элемент может требовать отдельной проверки, включая асинхронную.
import { array, string } from 'superstruct'
const CheckedItems = array(
refine(string(), 'ItemCheck', async (value) => {
const res = await fetch(`/api/item/${value}`)
const data = await res.json()
return data.exists
})
)
Каждый элемент массива проходит собственный асинхронный цикл, что может повлиять на производительность при больших объёмах данных.
При большом количестве асинхронных проверок возникает выбор между последовательным и параллельным выполнением.
await Promise.all(values.map(v => validate(v, Struct)))
for (const v of values) {
await validate(v, Struct)
}
Параллельная стратегия уменьшает общее время, но увеличивает нагрузку на внешние API. Последовательная — более стабильна при ограничениях по rate lim it.
Асинхронная валидация часто становится частью прикладной логики приложения, особенно при проверке прав доступа или состояния ресурсов.
const CanCreatePost = refine(string(), 'CanCreatePost', async (userId) => {
const res = await fetch(`/api/users/${userId}/permissions`)
const data = await res.json()
return data.canCreatePost
})
Такие структуры фактически объединяют слой валидации и слой авторизации.
Асинхронные проверки могут создавать значительную нагрузку при повторяющихся запросах. Для оптимизации применяется кэширование:
const cache = new Map()
const CachedUsernameCheck = refine(string(), 'CachedUsername', async (value) => {
if (cache.has(value)) {
return cache.get(value)
}
const res = await fetch(`/api/check/${value}`)
const data = await res.json()
cache.set(value, data.available)
return data.available
})
Кэширование снижает количество внешних запросов и стабилизирует поведение системы.
Асинхронная валидация особенно часто используется при обработке пользовательских форм, где необходимо проверять данные до их отправки или одновременно с отправкой.
async function submit(formData) {
const [error, value] = await validate(formData, User)
if (error) {
return { success: false, error }
}
await fetch('/api/create-user', {
method: 'POST',
body: JSON.stringify(value)
})
return { success: true }
}
Асинхронный характер позволяет встроить валидацию непосредственно в поток выполнения без блокировки интерфейса или логики приложения.