Обработка запросов

Работа с запросами в Slim Select строится вокруг механизма динамической загрузки данных, фильтрации и управления состоянием списка опций, получаемых с удалённого источника. Основная идея заключается в том, что компонент не ограничивается статическим набором <option> в DOM, а способен взаимодействовать с сервером, формировать запросы, обрабатывать ответы и преобразовывать их в структуру, пригодную для отображения.

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

  • формирование поискового запроса при вводе пользователя;
  • отправка HTTP-запроса к API;
  • получение и парсинг ответа;
  • преобразование данных в формат опций;
  • обновление состояния выпадающего списка;
  • управление кешированием и повторными запросами.

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

Инициализация с удалённой загрузкой

Типовой сценарий начинается с активации поиска и подключением функции загрузки данных:

new SlimSelect({
  select: '#cities',
  searchPlaceholder: 'Поиск...',
  ajax: (search, callback) => {
    fetch(`/api/cities?q=${encodeURIComponent(search)}`)
      .then(res => res.json())
      .then(data => {
        callback(data.items)
      })
  }
})

В данном случае ключевым элементом является функция ajax, которая принимает строку поиска и callback для возврата данных. Такой подход позволяет полностью контролировать процесс формирования запроса.

Формирование запросов к API

Запросы обычно зависят от пользовательского ввода. Для уменьшения нагрузки на сервер применяется ограничение частоты вызовов:

let timeout

function debounce(fn, delay) {
  return (...args) => {
    clearTimeout(timeout)
    timeout = setTimeout(() => fn(...args), delay)
  }
}

Использование debounce позволяет избежать отправки запроса на каждое нажатие клавиши, заменяя серию быстрых вызовов одним итоговым.

Интеграция с Slim Select:

new SlimSelect({
  select: '#users',
  ajax: debounce((search, callback) => {
    fetch(`/api/users?query=${search}`)
      .then(res => res.json())
      .then(data => callback(data.results))
  }, 300)
})

Форматирование ответа сервера

Slim Select ожидает данные в унифицированной структуре:

[
  { "text": "Almaty", "value": "almaty" },
  { "text": "Astana", "value": "astana" }
]

При несоответствии формата требуется трансформация:

fetch('/api/data')
  .then(res => res.json())
  .then(data => {
    return data.map(item => ({
      text: item.name,
      value: item.id
    }))
  })

Преобразование данных является обязательным этапом при интеграции с большинством REST API, поскольку серверные модели редко совпадают с клиентскими требованиями UI-компонента.

Обработка ошибок запросов

При работе с удалёнными источниками необходимо учитывать сбои сети, некорректные ответы и таймауты.

ajax: (search, callback) => {
  fetch(`/api/search?q=${search}`)
    .then(res => {
      if (!res.ok) throw new Error('Network error')
      return res.json()
    })
    .then(data => callback(data.items))
    .catch(() => callback([]))
}

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

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

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

let controller

new SlimSelect({
  select: '#products',
  ajax: (search, callback) => {
    if (controller) controller.abort()

    controller = new AbortController()

    fetch(`/api/products?q=${search}`, {
      signal: controller.signal
    })
      .then(res => res.json())
      .then(data => callback(data.items))
      .catch(() => {})
  }
})

Механизм AbortController предотвращает ситуацию, когда более медленный запрос перезаписывает результат более нового.

Кеширование результатов

При повторяющихся запросах к одним и тем же данным целесообразно использовать локальный кеш:

const cache = {}

function getData(query, callback) {
  if (cache[query]) {
    callback(cache[query])
    return
  }

  fetch(`/api/search?q=${query}`)
    .then(res => res.json())
    .then(data => {
      cache[query] = data.items
      callback(data.items)
    })
}

Кеширование снижает нагрузку на сервер и ускоряет повторные открытия ранее введённых запросов.

Пагинация удалённых данных

При больших объёмах данных сервер часто возвращает результаты порциями. Поддержка пагинации требует передачи дополнительных параметров:

let page = 1

new SlimSelect({
  select: '#logs',
  ajax: (search, callback) => {
    fetch(`/api/logs?q=${search}&page=${page}`)
      .then(res => res.json())
      .then(data => {
        callback(data.items)

        if (data.hasMore) {
          page++
        }
      })
  }
})

При таком подходе важно учитывать сброс страницы при новом поисковом запросе.

Синхронизация состояния и результатов

При изменении строки поиска необходимо сбрасывать предыдущие результаты и состояние пагинации:

let page = 1

function load(search, callback) {
  page = 1

  fetch(`/api/search?q=${search}&page=${page}`)
    .then(res => res.json())
    .then(data => callback(data.items))
}

Сброс состояния предотвращает смешивание результатов разных запросов.

Добавление заголовков и авторизация

При работе с защищёнными API часто требуется передача токена:

fetch('/api/private', {
  headers: {
    'Authorization': 'Bearer TOKEN_VALUE',
    'Content-Type': 'application/json'
  }
})
  .then(res => res.json())
  .then(data => console.log(data))

Slim Select не ограничивает способ формирования запроса, поэтому любая схема авторизации интегрируется на уровне fetch-логики.

Серверная фильтрация и оптимизация

Эффективная обработка запросов предполагает перенос фильтрации на сервер. Клиент в этом случае отправляет только строку поиска, избегая локальной обработки больших массивов данных.

Типичная схема запроса:

/api/items?search=abc&limit=20

Сервер возвращает уже отфильтрованный набор, что уменьшает объём передаваемых данных и ускоряет рендеринг компонента.

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

Некоторые API возвращают данные постепенно. В таком случае требуется аккуратное обновление списка без полной перерисовки:

ajax: (search, callback) => {
  fetch(`/api/stream?q=${search}`)
    .then(res => res.json())
    .then(data => {
      const formatted = data.items.map(i => ({
        text: i.title,
        value: i.id
      }))

      callback(formatted)
    })
}

Подобный подход используется при интеграции с системами рекомендаций и поисковыми движками.

Управление конкурентными запросами

При интенсивном вводе возникает проблема гонки ответов. Даже при использовании AbortController важно учитывать порядок обработки:

  • последний запрос должен иметь приоритет;
  • устаревшие ответы не должны попадать в UI;
  • состояние компонента должно обновляться атомарно.

Решение через идентификатор запроса:

let requestId = 0

function load(search, callback) {
  const currentId = ++requestId

  fetch(`/api?q=${search}`)
    .then(res => res.json())
    .then(data => {
      if (currentId !== requestId) return
      callback(data.items)
    })
}

Такой механизм гарантирует консистентность данных независимо от скорости сети.

Интеграция с нестандартными API

Не все серверы возвращают JSON. Иногда требуется обработка текстовых или XML-ответов:

fetch('/api/xml')
  .then(res => res.text())
  .then(str => {
    const parser = new DOMParser()
    const xml = parser.parseFromString(str, 'application/xml')

    const items = [...xml.querySelectorAll('item')].map(node => ({
      text: node.textContent,
      value: node.getAttribute('id')
    }))

    return items
  })

Slim Select в таком случае получает уже нормализованные данные, не зависящие от формата исходного API.

Оптимизация сетевых запросов

Для повышения производительности применяются дополнительные техники:

  • объединение запросов при быстром вводе;
  • ограничение минимальной длины строки поиска;
  • предварительная загрузка популярных значений;
  • использование HTTP-кеширования.

Минимальная длина запроса:

if (search.length < 2) {
  callback([])
  return
}

Такая проверка снижает количество бессмысленных обращений к серверу.

Итоговая модель обработки

Обработка запросов в Slim Select представляет собой многослойную систему, где UI-компонент выступает лишь интерфейсом отображения, а вся логика получения данных вынесена в пользовательские обработчики. Это обеспечивает гибкость интеграции с любыми backend-архитектурами — от простых REST API до потоковых и событийных систем.