Асинхронная валидация в JavaScript-приложениях возникает там, где проверка данных не может быть выполнена локально и требует обращения к внешним источникам: базе данных, API, сторонним сервисам. В отличие от синхронных проверок, такие операции связаны с задержками и должны строиться на основе Promise, async/await или callback-подходов.
Библиотека validator.js предоставляет богатый набор синхронных валидаторов для строковых данных (email, URL, длина, числовые проверки и т.д.), однако не включает встроенный механизм асинхронных проверок. Это формирует архитектурный паттерн: синхронная валидация выполняется внутри Validator.js, а асинхронная логика реализуется обёртками вокруг него.
Синхронные валидаторы работают мгновенно и не зависят от внешних систем:
import validator from 'validator';
validator.isEmail('test@example.com'); // true
validator.isLength('hello', { min: 2, max: 10 }); // true
Асинхронные проверки появляются, когда требуется:
Асинхронная валидация строится вокруг Promise:
async function validateEmailUniqueness(email) {
const response = await fetch(`/api/users/check-email?email=${email}`);
const result = await response.json();
return result.isAvailable;
}
Комбинирование с Validator.js происходит до или после асинхронной стадии:
import validator from 'validator';
async function validateUserEmail(email) {
if (!validator.isEmail(email)) {
return { valid: false, error: 'Некорректный формат email' };
}
const isUnique = await validateEmailUniqueness(email);
if (!isUnique) {
return { valid: false, error: 'Email уже используется' };
}
return { valid: true };
}
Типовая архитектура включает два слоя:
function basicValidation(data) {
return validator.isLength(data.username, { min: 3, max: 20 });
}
async function asyncValidation(data) {
const res = await fetch(`/api/check-username?u=${data.username}`);
return res.ok;
}
async function validate(data) {
if (!basicValidation(data)) return false;
return await asyncValidation(data);
}
Такой подход снижает нагрузку на сеть, так как внешние запросы выполняются только при прохождении локальных правил.
Асинхронная валидация в UI часто сталкивается с проблемой устаревших запросов. Например, пользователь вводит имя, и отправляется несколько запросов подряд.
let lastRequestId = 0;
async function validateUsername(username) {
const requestId = ++lastRequestId;
const response = await fetch(`/api/check?username=${username}`);
const result = await response.json();
if (requestId !== lastRequestId) {
return null; // игнор устаревшего результата
}
return result.available;
}
Validator.js здесь используется только для предварительного отсечения некорректных значений:
if (!validator.isAlphanumeric(username)) {
return false;
}
Для предотвращения избыточных запросов применяется debounce:
function debounce(fn, delay) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => fn.apply(this, args), delay);
};
}
Применение:
const validateUsernameDebounced = debounce(async (username) => {
if (!validator.isLength(username, { min: 3 })) return;
const res = await fetch(`/api/check?u=${username}`);
const data = await res.json();
console.log(data.available);
}, 400);
Асинхронная валидация часто используется в формах регистрации.
async function validateForm(form) {
const errors = {};
if (!validator.isEmail(form.email)) {
errors.email = 'Email некорректен';
} else {
const res = await fetch(`/api/email?e=${form.email}`);
const data = await res.json();
if (!data.free) {
errors.email = 'Email занят';
}
}
if (!validator.isLength(form.password, { min: 8 })) {
errors.password = 'Пароль слишком короткий';
}
return errors;
}
Здесь Validator.js выполняет роль первого фильтра, уменьшая количество сетевых вызовов.
Расширяемая модель строится через композицию функций:
function composeAsyncValidators(validators) {
return async (value) => {
for (const validatorFn of validators) {
const result = await validatorFn(value);
if (result !== true) return result;
}
return true;
};
}
Пример использования:
const usernameValidator = composeAsyncValidators([
(v) => validator.isAlphanumeric(v) || 'Только буквы и цифры',
async (v) => {
const res = await fetch(`/api/check?v=${v}`);
const data = await res.json();
return data.available || 'Имя занято';
}
]);
Для уменьшения нагрузки применяется кэш:
const cache = new Map();
async function cachedCheckEmail(email) {
if (cache.has(email)) {
return cache.get(email);
}
const res = await fetch(`/api/email?e=${email}`);
const data = await res.json();
cache.set(email, data.isFree);
return data.isFree;
}
Перед кэшированием часто выполняется синтаксическая проверка через Validator.js:
if (!validator.isEmail(email)) return false;
Асинхронная валидация должна учитывать нестабильность сети:
async function safeValidate(email) {
if (!validator.isEmail(email)) {
return { valid: false, reason: 'format' };
}
try {
const res = await fetch(`/api/check?email=${email}`);
const data = await res.json();
return { valid: data.ok };
} catch (e) {
return { valid: false, reason: 'network' };
}
}
При сложных формах проверки выполняются параллельно:
async function validateAll(data) {
const [emailRes, usernameRes] = await Promise.all([
validateEmailUniqueness(data.email),
fetch(`/api/check-username?u=${data.username}`).then(r => r.json())
]);
return {
email: emailRes,
username: usernameRes.available
};
}
Validator.js используется до запуска Promise.all для быстрой фильтрации:
if (!validator.isEmail(data.email)) {
return { email: false };
}