Практическое задание: реализовать компонент автокомплита (поиска с подсказками) — с debounce для ввода, отменой предыдущего запроса через AbortController, отображением состояния загрузки, списком подсказок и обработкой выбора элемента из списка по клику.

Сразу отменяйте старый запрос при новом вводе, откладывайте новый запуск debounce-таймером и дополнительно проверяйте номер актуальной операции.

Короткий ответ

Что ответить на собеседовании

Сразу отменяйте старый запрос при новом вводе, откладывайте новый запуск debounce-таймером и дополнительно проверяйте номер актуальной операции.

Подробный разбор

Ответ с пояснениями

Сразу отменяйте старый запрос при новом вводе, откладывайте новый запуск debounce-таймером и дополнительно проверяйте номер актуальной операции.

Ниже минимальный вариант без фреймворка. search(query, signal) должен вернуть Promise массива объектов {id, label} и использовать signal в fetch. Контракт API адаптируется отдельно.

function autocomplete(root, search, onSelect) {
  const input = document.createElement('input');
  const status = document.createElement('p');
  const list = document.createElement('ul');
  input.setAttribute('aria-label', 'Поиск');
  status.setAttribute('aria-live', 'polite');
  root.replaceChildren(input, status, list);
  let timer, controller, version = 0;
  function cancel() {
    version++;
    clearTimeout(timer);
    controller?.abort();
  }
  function changed() {
    cancel();
    list.replaceChildren();
    const query = input.value.trim();
    status.textContent = '';
    if (!query) return;
    const current = version;
    timer = setTimeout(async () => {
      controller = new AbortController();
      status.textContent = 'Загрузка…';
      try {
        const items = await search(query, controller.signal);
        if (current !== version) return;
        if (!Array.isArray(items) || items.some(item =>
          !item || typeof item.label !== 'string' ||
          !['string', 'number'].includes(typeof item.id))) {
          throw new Error('Некорректные подсказки');
        }
        status.textContent = items.length ? '' : 'Ничего не найдено';
        for (const item of items) {
          const row = document.createElement('li');
          const button = document.createElement('button');
          button.type = 'button';
          button.textContent = item.label;
          button.onclick = () => {
            cancel();
            input.value = item.label;
            list.replaceChildren();
            status.textContent = '';
            input.focus();
            onSelect(item);
          };
          row.append(button);
          list.append(row);
        }
      } catch (error) {
        if (current === version && error?.name !== 'AbortError') {
          status.textContent = 'Не удалось загрузить подсказки';
        }
      }
    }, 300);
  }
  input.addEventListener('input', changed);
  return () => {
    cancel();
    input.removeEventListener('input', changed);
    root.replaceChildren();
  };
}

Номер операции защищает и от источника, игнорирующего abort. Подсказки выводятся через textContent, не HTML; кнопки доступны через Tab. Для полноценного combobox отдельно добавляют согласованную клавиатурную модель и ARIA-состояния.

ИИ-помощник для собеседований

Хочешь уверенно проходить собеседования?

Попробуй ИИ-помощник для собеседований: слышит вас и собеседника, анализирует экран, подсказывает ответы в реальном времени, работает без VPN и не попадает в захват экрана.

Подробнее