Очистка ресурсов

Сброс состояния валидации и предотвращение накопления данных

Любая система валидации в JavaScript, работающая с динамическими формами, вынуждена решать проблему накопления состояния. При многократном выполнении правил, особенно в реактивных интерфейсах, формируется набор промежуточных данных: результаты проверок, ошибки, статусы полей, асинхронные запросы. Если это состояние не очищать, оно начинает влиять на последующие вычисления и приводит к утечкам памяти и неконсистентным результатам.

В Vest модель выполнения основана на декларативных наборах правил (suites), которые пересчитываются при каждом запуске. Поэтому очистка ресурсов в первую очередь связана не с удалением «объектов в памяти», а с приведением состояния выполнения к предсказуемому начальному виду.

Основные цели очистки:

  • сброс результатов предыдущих запусков;
  • отмена незавершённых асинхронных проверок;
  • удаление временных ошибок и метаданных;
  • разрыв связей с внешними подписками (например, UI-слоем).

Сброс результатов выполнения suite

Каждый набор правил в Vest формирует структуру состояния, содержащую ошибки и валидные/невалидные поля. При повторном запуске без очистки старые ошибки могут сохраняться, если они не были перезаписаны.

Типовой подход заключается в том, что перед повторной валидацией состояние приводится к «пустому» виду.

import { create, test } from "vest";

const suite = create((data = {}) => {
  test("username", "Invalid username", () => {
    if (!data.username || data.username.length < 3) {
      throw "Too short";
    }
  });
});

При повторных вызовах важно учитывать, что результаты предыдущего запуска логически устаревают. Поэтому применяется либо пересоздание suite, либо явный сброс состояния через управляющие механизмы библиотеки.


Очистка при реактивных сценариях

В SPA-приложениях suite часто вызывается внутри эффектов UI-фреймворков. При смене компонента или размонтировании необходимо предотвратить накопление фоновых процессов.

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

import { create, test } from "vest";

const suite = create((data = {}, ctx = {}) => {
  test("email", "Email is taken", async () => {
    const response = await fetch(`/api/check?email=${data.email}`);
    const result = await response.json();

    if (result.exists) {
      throw "Email already exists";
    }
  });
});

Если такой suite запускается многократно, без контроля жизненного цикла, возможно завершение запроса уже после смены контекста формы.


Отмена асинхронных проверок

Асинхронные проверки являются основным источником «висящих» ресурсов. Очистка в этом случае означает не уничтожение результата, а прекращение его дальнейшей обработки.

Распространённый подход — использование AbortController:

const controller = new AbortController();

const suite = create((data = {}) => {
  test("username", "Unavailable", async () => {
    const res = await fetch("/api/check-username", {
      signal: controller.signal
    });

    if (!res.ok) return;

    const json = await res.json();
    if (!json.available) {
      throw "Taken";
    }
  });
});

При смене формы или размонтировании логики контроллер должен быть завершён:

controller.abort();

Это предотвращает продолжение сетевых запросов и исключает обработку устаревших результатов.


Изоляция контекстов выполнения

В сложных приложениях один и тот же suite может запускаться в разных контекстах (например, разные формы или шаги мастера). Если состояние не изолировано, результаты одного запуска могут влиять на другой.

Практика очистки здесь заключается в создании нового экземпляра suite вместо переиспользования старого:

  • каждый контекст получает собственную фабрику;
  • предыдущие экземпляры становятся кандидатами на сборку мусора;
  • исключается случайное разделение состояния.
const createLoginSuite = () =>
  create((data = {}) => {
    test("password", "Required", () => {
      if (!data.password) throw "Missing password";
    });
  });

const suiteA = createLoginSuite();
const suiteB = createLoginSuite();

Очистка кешированных результатов

Некоторые реализации используют кеширование результатов тестов для ускорения повторных проверок. Однако кеш становится источником устаревших данных, если входные параметры изменились, а ключи кеша не обновились.

Очистка кеша в таких системах обычно привязана к:

  • смене идентификатора формы;
  • изменению набора правил;
  • явному сбросу состояния приложения.

Стратегии:

  • полный сброс кеша при unmount;
  • версионирование ключей кеша;
  • привязка кеша к входным данным.

Управление подписками и побочными эффектами

Валидационные системы нередко интегрируются с внешними событиями: input events, store subscriptions, реактивные сигналы. Каждая подписка является потенциальным источником утечек.

Очистка в этом контексте означает обязательное отписывание:

const unsubscribe = store.subscribe(() => {
  suite(store.getState());
});

// при завершении работы формы
unsubscribe();

Если этого не сделать, suite продолжит вызываться даже после уничтожения UI.


Сброс состояния между итерациями разработки

При частых перезапусках приложения в режиме разработки старые состояния могут сохраняться в памяти модуля. Это особенно заметно при hot module replacement.

Подходы к очистке:

  • пересоздание suite при каждом обновлении модуля;
  • явный reset внутренних структур;
  • изоляция состояния в фабриках.
if (import.meta.hot) {
  import.meta.hot.dispose(() => {
    // очистка перед заменой модуля
  });
}

Управление жизненным циклом как часть архитектуры

Очистка ресурсов в Vest-ориентированных системах не является вспомогательной задачей. Она встроена в архитектурную модель, где suite — это временный вычислительный процесс, а не долгоживущий объект.

Основные принципы:

  • suite должен быть либо воспроизводимым, либо уничтожаемым;
  • результаты не должны жить дольше контекста, в котором они вычислены;
  • асинхронные операции всегда должны иметь механизм остановки;
  • внешние подписки должны быть симметрично очищены.

Такая модель позволяет удерживать предсказуемость системы даже при высокой частоте пересчётов и сложной реактивной логике.