Асинхронные проверки валидации в Vest строятся вокруг промисов как базового механизма представления отложенного результата. Любая операция, зависящая от внешнего источника данных — сетевой запрос, обращение к базе, проверка уникальности значения — неизбежно выходит за пределы синхронного исполнения, и тестовая модель должна учитывать три состояния: ожидание, успешное завершение и отклонение.
Внутри тестовой системы промис становится не просто способом получения результата, а частью контрактной модели: тест считается завершённым только после разрешения всех асинхронных зависимостей.
Асинхронный тест валидации фактически описывает правило:
Если результат промиса не обработан, тестовая система не может зафиксировать итог состояния, что приводит либо к зависанию, либо к ложноположительным результатам.
Базовая форма асинхронного теста:
const suite = createSuite("user validation", (data) => {
test("email", "email already exists", async () => {
return checkEmailExists(data.email);
});
});
Функция checkEmailExists возвращает промис, который либо
резолвится (валидное состояние), либо реджектится (ошибка
валидации).
Внутри системы валидации промис интерпретируется не как просто результат, а как источник состояния поля. Поведение определяется следующим образом:
Особенность подхода заключается в том, что состояние
pending должно быть управляемым. Это означает, что система
должна агрегировать все активные промисы перед финальной фиксацией
результата.
Пример обработки нескольких асинхронных правил:
test("username", "must be unique", () => {
return Promise.all([
checkUsernameAvailability(value),
checkBlacklist(value),
]);
});
Здесь важна деталь: отклонение любого промиса приводит к провалу теста, поэтому композиция промисов становится частью логики валидации.
Использование async/await упрощает чтение тестов, но не
изменяет их семантику. Внутри тестового раннера async
функция преобразуется в промис автоматически.
test("email format", "invalid email", async () => {
const result = await validateEmail(value);
return result;
});
В данном случае возврат результата не требует явного создания промиса — функция уже возвращает его неявно.
Ключевой момент заключается в том, что любая ошибка, выброшенная
внутри async функции, автоматически трансформируется в
rejected промис:
test("password strength", "too weak password", async () => {
const score = await calculateStrength(value);
if (score < 3) {
throw new Error("weak");
}
return true;
});
При проверке нескольких полей одновременно возникает проблема конкурентного исполнения. Валидационные промисы могут выполняться параллельно, но результат должен быть консистентным.
Типичная модель исполнения:
const result = await validate(data);
console.log(result.hasErrors());
Если хотя бы один промис ещё находится в состоянии pending, результат не считается финальным.
Одной из ключевых проблем является устаревание результатов. Если пользователь изменяет значение поля быстрее, чем завершается предыдущий промис, возникает ситуация гонки.
Пример:
let currentRequestId = 0;
test("username", "invalid", () => {
const requestId = ++currentRequestId;
return checkUsername(value).then((res) => {
if (requestId !== currentRequestId) {
return;
}
return res;
});
});
Такая конструкция предотвращает применение устаревшего результата к актуальному состоянию поля.
В реальных системах валидации это часто решается через отмену запросов или игнорирование результатов по версии запроса.
Промисы сами по себе не отменяемы, поэтому управление их жизненным циклом требует дополнительных механизмов: AbortController или внутренние флаги состояния.
test("email", "invalid", ({ signal }) => {
return fetch(`/api/check?email=${value}`, { signal })
.then((r) => r.json());
});
При интеграции с системой валидации важно, чтобы отмена запроса не приводила к изменению состояния после завершения теста.
Валидационная система оперирует набором тестов, каждый из которых может возвращать промис. Результат агрегируется в единый объект состояния.
Логика объединения:
Promise.all над всеми
тестамиconst result = await Promise.all(validationTasks);
При этом важна деталь: ошибка одного промиса не должна прерывать выполнение остальных проверок, если система поддерживает полную диагностику.
Частое обновление данных приводит к множественным промисам, что перегружает систему. Для этого вводится механизм дебаунсинга асинхронных тестов.
const debouncedCheck = debounce((value) => {
return checkEmail(value);
}, 300);
В контексте валидации это означает, что запуск промиса откладывается до стабилизации входного значения.
Тестовая модель допускает смешивание синхронных и асинхронных правил. В этом случае система должна унифицировать результат в промис, чтобы обеспечить единый интерфейс обработки.
test("username", "required", () => {
if (!value) return false;
});
test("username", "unique", async () => {
return checkUnique(value);
});
Даже синхронный тест интерпретируется как мгновенно резолвящийся промис, что упрощает агрегирование.
Внутренний поток выполнения можно описать как последовательность стадий:
Каждый этап зависит от предыдущего, но асинхронные операции выполняются параллельно, что повышает производительность при большом числе проверок.
Любое исключение внутри промиса должно быть приведено к унифицированному виду ошибки. Это необходимо для того, чтобы система валидации могла агрегировать результаты без потери информации.
test("password", "invalid", async () => {
try {
return await validatePassword(value);
} catch (e) {
return Promise.reject("validation failed");
}
});
Нормализация ошибок позволяет различать типы сбоев: сетевые, логические, пользовательские.
При изменении входных данных все активные промисы должны быть либо завершены с игнорированием результата, либо пересозданы. Повторное выполнение является ключевым механизмом реактивной валидации.
function runValidation(data) {
return validate(data);
}
Каждый вызов инициирует новый набор промисов, что исключает загрязнение состояния между итерациями.
Основная сложность асинхронной валидации заключается в необходимости поддерживать консистентность при одновременном выполнении нескольких промисов. Любое несогласованное завершение может привести к частично обновлённому состоянию.
Для этого используется правило:
Это предотвращает появление промежуточных ошибок в пользовательском интерфейсе и обеспечивает атомарность результата проверки.