Сразу отменяйте старый запрос при новом вводе, откладывайте новый запуск debounce-таймером и дополнительно проверяйте номер актуальной операции.
Практическое задание: реализовать компонент автокомплита (поиска с подсказками) — с debounce для ввода, отменой предыдущего запроса через AbortController, отображением состояния загрузки, списком подсказок и обработкой выбора элемента из списка по клику.
Сразу отменяйте старый запрос при новом вводе, откладывайте новый запуск 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-состояния.