validator.js изначально ориентирован на проверку отдельных строковых значений, однако в реальных приложениях почти всегда возникает необходимость работать с наборами данных: списками пользователей, массивами форм, пакетами импортируемых записей, результатами API или потоками данных из внешних систем. Пакетная обработка в этом контексте означает применение набора правил валидации к коллекциям значений с последующей агрегацией результатов, оптимизацией повторного использования проверок и контролем производительности при больших объёмах входных данных.
На уровне архитектуры пакетная обработка строится вокруг трёх операций:
Типовая структура:
import validator from "validator";
function validateItem(item) {
const errors = [];
if (!validator.isEmail(item.email)) {
errors.push("Некорректный email");
}
if (!validator.isLength(item.password, { min: 8 })) {
errors.push("Пароль слишком короткий");
}
return errors;
}
function validateBatch(data) {
return data.map((item, index) => ({
index,
errors: validateItem(item)
}));
}
Такой подход масштабируется линейно, но требует аккуратного управления производительностью при увеличении объёма данных.
Наиболее частый случай — проверка массива строк или чисел:
function validateEmails(emails) {
return emails.map(email => ({
value: email,
valid: validator.isEmail(email)
}));
}
При работе с большими массивами важно избегать лишних преобразований данных внутри цикла. Например, нормализация строк (trim, lowercase) должна выполняться один раз до валидации:
const normalized = emails.map(e => e.trim().toLowerCase());
Реальные системы чаще оперируют не примитивами, а объектами:
const users = [
{ email: "test@mail.com", age: "25" },
{ email: "invalid@", age: "not-a-number" }
];
В этом случае формируется единый слой правил:
function validateUser(user) {
const errors = [];
if (!validator.isEmail(user.email)) {
errors.push("email");
}
if (!validator.isInt(user.age, { min: 0 })) {
errors.push("age");
}
return errors;
}
Пакетная обработка:
function validateUsers(users) {
const result = [];
for (let i = 0; i < users.length; i++) {
const errors = validateUser(users[i]);
result.push({
index: i,
valid: errors.length === 0,
errors
});
}
return result;
}
При работе с массивами данных важен не только факт ошибки, но и её структура. Обычно применяются три модели:
Ошибки привязываются к индексу элемента:
{
index: 3,
errors: ["email", "password"]
}
Ошибки группируются по идентификатору:
{
id: "user_42",
errors: ["email"]
}
Ошибки структурируются по полям:
{
email: "invalid",
password: "too short"
}
Последний вариант наиболее удобен для UI-слоя и API-ответов.
Validator.js предоставляет набор независимых функций, которые удобно комбинировать:
function composeValidators(value, rules) {
const errors = [];
if (rules.email && !validator.isEmail(value.email)) {
errors.push("email");
}
if (rules.minLength && !validator.isLength(value.name, { min: rules.minLength })) {
errors.push("name");
}
return errors;
}
Пакетная обработка:
function validateBatch(data, rules) {
return data.map(item => composeValidators(item, rules));
}
При обработке больших массивов критично избегать повторного создания функций и объектов правил. Вместо этого применяются предсобранные валидаторы:
const isValidEmail = validator.isEmail;
const isValidInt = validator.isInt;
function validate(item) {
const errors = [];
if (!isValidEmail(item.email)) errors.push("email");
if (!isValidInt(item.age)) errors.push("age");
return errors;
}
Такой подход снижает накладные расходы на вызовы и улучшает предсказуемость выполнения.
При обработке больших массивов (десятки тысяч записей и более)
использование map может приводить к росту потребления
памяти. Альтернативой становится потоковая обработка:
function validateStream(data) {
const result = [];
for (const item of data) {
result.push({
valid: validateUser(item).length === 0
});
}
return result;
}
При необходимости можно добавлять ранний выход:
if (errors.length > 1000) break;
В некоторых сценариях не требуется полная проверка всего набора данных. Используются стратегии частичной валидации:
Пример выборочной проверки:
function validateSample(data, step = 10) {
const result = [];
for (let i = 0; i < data.length; i += step) {
result.push(validateUser(data[i]));
}
return result;
}
Основные факторы производительности:
Каждый вызов validator.isEmail или
validator.isLength имеет накладные расходы. При пакетной
обработке они умножаются на размер коллекции.
Удаление пробелов, приведение регистра и фильтрация пустых значений до этапа валидации снижает количество ложных проверок.
Если одни и те же значения встречаются многократно:
const cache = new Map();
function cachedIsEmail(value) {
if (cache.has(value)) return cache.get(value);
const result = validator.isEmail(value);
cache.set(value, result);
return result;
}
В сложных системах данные часто смешанные: часть приходит из API, часть из пользовательского ввода, часть из файлов. В таких случаях вводится единый слой адаптации:
function normalize(item) {
return {
email: String(item.email || "").trim(),
age: String(item.age || "")
};
}
function validateBatch(data) {
return data.map(item => validateUser(normalize(item)));
}
После завершения проверки формируется итоговая структура:
{
total: 1000,
valid: 850,
invalid: 150,
errors: [...]
}
Агрегация выполняется отдельно от самой валидации:
function summarize(results) {
return results.reduce(
(acc, item) => {
item.valid ? acc.valid++ : acc.invalid++;
return acc;
},
{ valid: 0, invalid: 0 }
);
}
Пакетная обработка требует стабильного поведения функций валидации. Любые побочные эффекты (изменение состояния, зависимость от времени, внешних источников) приводят к несогласованным результатам при повторных запусках.
Поэтому валидаторы должны оставаться чистыми функциями:
function isValid(item) {
return validator.isEmail(item.email);
}
Несмотря на отсутствие встроенной схемной системы, Validator.js часто используется как основа для её построения:
const schema = {
email: validator.isEmail,
age: value => validator.isInt(value, { min: 0 })
};
function validateBySchema(item, schema) {
const errors = [];
for (const key in schema) {
if (!schema[key](item[key])) {
errors.push(key);
}
}
return errors;
}
При росте объёмов данных применяются дополнительные уровни:
Пример чанкинга:
function chunk(array, size) {
const result = [];
for (let i = 0; i < array.length; i += size) {
result.push(array.slice(i, i + size));
}
return result;
}
В системах с критичными данными применяется принцип атомарности пакета: либо весь пакет валиден, либо отклоняется целиком. Для этого вводится предварительный проход:
const invalid = batch.some(item => validateUser(item).length > 0);
Если обнаружены ошибки, дальнейшая обработка прекращается.