Обработка ошибок загрузки

В сценариях работы с удалёнными источниками данных Tom Select опирается на функцию загрузки (load), которая отвечает за получение и преобразование данных перед отображением вариантов. На этом уровне формируется ключевая зона нестабильности: сетевые сбои, некорректные ответы сервера, таймауты, ошибки формата данных.

Базовая сигнатура загрузчика:

new TomSelect("#select", {
  valueField: "id",
  labelField: "title",
  searchField: "title",

  load: function(query, callback) {
    fetch(`/api/items?q=${encodeURIComponent(query)}`)
      .then(res => res.json())
      .then(data => callback(data))
      .catch(() => callback());
  }
});

В данной схеме обработка ошибок фактически сводится к вызову callback() без аргументов. Это приводит к пустому результату, но не даёт информации о природе сбоя, что критично при сложной интеграции.

Типовые классы ошибок при загрузке

Сетевые ошибки

Возникают при недоступности API, обрыве соединения или DNS-проблемах. В fetch такие ситуации попадают в catch.

Характерные признаки:

  • отсутствие HTTP-ответа
  • исключения уровня сети
  • таймаут соединения (при ручной реализации)

Пример расширенной обработки:

load: function(query, callback) {
  fetch(`/api/items?q=${encodeURIComponent(query)}`)
    .then(res => {
      if (!res.ok) {
        throw new Error(`HTTP ${res.status}`);
      }
      return res.json();
    })
    .then(data => callback(data))
    .catch(err => {
      console.error("Load error:", err);
      callback();
    });
}

Ошибки HTTP-статусов

Даже при успешном сетевом соединении сервер может возвращать:

  • 400 — некорректный запрос
  • 401/403 — ошибки авторизации
  • 500 — внутренняя ошибка сервера

Tom Select не интерпретирует HTTP-статусы автоматически, поэтому контроль состояния ответа полностью ложится на пользовательскую реализацию загрузчика.

Ошибки формата данных

Часто встречается ситуация, когда сервер возвращает JSON, не соответствующий ожиданиям valueField и labelField.

Пример некорректной структуры:

[
  { "name": "Item 1" }
]

При ожидании:

[
  { "id": 1, "title": "Item 1" }
]

Результат:

  • пустой список
  • отсутствие ошибок в консоли Tom Select
  • некорректная работа фильтрации

Для диагностики применяется валидация:

.then(data => {
  if (!Array.isArray(data)) {
    throw new Error("Invalid data format");
  }
  callback(data);
})

Управление ошибками через callback-модель Tom Select

Tom Select использует callback как основной механизм завершения загрузки:

callback(items);
callback();

Особенность модели заключается в отсутствии отдельного канала передачи ошибки. Это создаёт необходимость внедрения дополнительных соглашений.

Паттерн «пустой результат + логирование»

.catch(err => {
  logError(err);
  callback();
});

Недостаток: отсутствие обратной связи для UI.

Паттерн «результат-обёртка»

Используется кастомная структура:

.then(data => {
  callback(data.items || []);
})

При этом сервер возвращает:

{
  "items": [],
  "error": null
}

Позволяет централизовать обработку ошибок вне компонента.

Интеграция с fetch и AbortController

При динамическом поиске частая проблема — устаревшие запросы. Tom Select может инициировать множество загрузок при быстром вводе текста.

Решение — отмена предыдущего запроса:

let controller = null;

load: function(query, callback) {
  if (controller) controller.abort();

  controller = new AbortController();

  fetch(`/api/items?q=${encodeURIComponent(query)}`, {
    signal: controller.signal
  })
    .then(res => res.json())
    .then(data => callback(data))
    .catch(err => {
      if (err.name === "AbortError") return;
      callback();
    });
}

Ключевой эффект:

  • предотвращение гонок запросов
  • исключение устаревших данных
  • снижение нагрузки на сервер

Обработка таймаутов

Встроенного таймаута в стандартной конфигурации нет, поэтому используется комбинация Promise.race.

function fetchWithTimeout(url, timeout = 5000) {
  return Promise.race([
    fetch(url),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error("Timeout")), timeout)
    )
  ]);
}

Интеграция в Tom Select:

load: function(query, callback) {
  fetchWithTimeout(`/api/items?q=${query}`)
    .then(res => res.json())
    .then(data => callback(data))
    .catch(() => callback());
}

Поведение при пустых результатах

Пустой массив данных не является ошибкой, однако часто интерпретируется как сбой UX.

Различают два состояния:

  • ошибка загрузки
  • отсутствие совпадений

Корректная реализация:

.then(data => {
  if (data.length === 0) {
    callback([]);
    return;
  }
  callback(data);
})

Дополнительно возможно формирование пользовательских опций:

callback([
  { id: "", title: "Ничего не найдено" }
]);

Централизованная обработка ошибок через wrapper

Для сложных систем применяется обёртка над загрузчиком:

function createSafeLoader(loader) {
  return function(query, callback) {
    try {
      loader(query, callback);
    } catch (e) {
      console.error(e);
      callback();
    }
  };
}

Использование:

new TomSelect("#select", {
  load: createSafeLoader(function(query, callback) {
    fetch(`/api?q=${query}`)
      .then(r => r.json())
      .then(callback)
      .catch(() => callback());
  })
});

Логирование ошибок загрузки

При интеграции с аналитическими системами ошибки загрузки часто фиксируются отдельно от UI-обработки.

Пример расширенного логирования:

.catch(err => {
  logToService({
    type: "tomselect_load_error",
    message: err.message,
    query
  });

  callback();
});

Фиксируемые параметры:

  • поисковый запрос
  • HTTP статус
  • время ответа
  • тип ошибки

Повторные попытки загрузки (retry)

При нестабильной сети применяется стратегия повторов:

function fetchRetry(url, retries = 3) {
  return fetch(url).catch(err => {
    if (retries > 0) {
      return fetchRetry(url, retries - 1);
    }
    throw err;
  });
}

Интеграция:

load: function(query, callback) {
  fetchRetry(`/api/items?q=${query}`)
    .then(r => r.json())
    .then(data => callback(data))
    .catch(() => callback());
}

Синхронизация состояния загрузки

Tom Select предоставляет событие load, которое позволяет отслеживать момент завершения загрузки данных.

const select = new TomSelect("#select", {
  load: function(query, callback) {
    callback([]);
  }
});

select.on("load", () => {
  console.log("Загрузка завершена");
});

На основе этого механизма строится:

  • индикатор загрузки
  • блокировка UI
  • аналитика времени ответа

Ошибки сериализации и нестабильные API

При работе с внешними API часто встречаются:

  • некорректный JSON
  • обрезанные ответы
  • HTML вместо JSON

Защита:

.then(async res => {
  const text = await res.text();

  try {
    return JSON.parse(text);
  } catch {
    throw new Error("Invalid JSON response");
  }
})

Итоговая модель устойчивой загрузки

Стабильная обработка ошибок в Tom Select формируется из нескольких уровней:

  • проверка HTTP-статуса
  • контроль структуры данных
  • защита от гонок запросов
  • обработка таймаутов
  • логирование
  • повторные попытки
  • безопасный callback-слой

Комбинация этих механизмов формирует предсказуемое поведение компонента даже при нестабильной сетевой среде и неполностью контролируемом серверном API.