При работе с Awesomplete ключевым ограничением становится необходимость заранее передавать список данных. Для небольших наборов это не вызывает проблем, но при тысячах или миллионах записей клиентская загрузка становится неэффективной. Решение заключается в переходе к асинхронной модели, где данные запрашиваются динамически при вводе текста.
Базовый подход строится вокруг события ввода и вызова серверного API:
const input = document.querySelector("#city");
const awesomplete = new Awesomplete(input, {
minChars: 2,
maxItems: 10
});
input.addEventListener("input", function () {
const query = this.value;
if (query.length < 2) return;
fetch(`/api/cities?q=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(data => {
awesomplete.list = data;
});
});
Здесь список обновляется после каждого изменения поля ввода. Однако такой подход создаёт избыточное количество запросов, поэтому требуется контроль частоты обращений.
Асинхронная подгрузка без ограничения приводит к перегрузке сети и сервера. Для стабилизации используется техника debounce, при которой запрос отправляется только после паузы в наборе текста.
function debounce(fn, delay) {
let timer;
return function (...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
Применение:
const loadCities = debounce(function (query) {
fetch(`/api/cities?q=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(data => {
awesomplete.list = data;
});
}, 300);
input.addEventListener("input", function () {
if (this.value.length >= 2) {
loadCities(this.value);
}
});
Такой механизм снижает количество запросов и делает поведение интерфейса предсказуемым.
Awesomplete поддерживает возможность передачи функции вместо статического массива. Это позволяет полностью контролировать процесс формирования подсказок.
new Awesomplete(input, {
minChars: 2,
list: function (text, callback) {
fetch(`/api/cities?q=${encodeURIComponent(text)}`)
.then(res => res.json())
.then(data => callback(data));
}
});
В этом случае библиотека сама инициирует запрос к списку через callback, а разработчик отвечает только за получение данных.
Такой подход имеет важное преимущество: исключается необходимость
вручную присваивать awesomplete.list, а логика интеграции
становится более декларативной.
При асинхронных запросах возникает проблема устаревших ответов. Сервер может вернуть данные позже, чем был отправлен новый запрос, и интерфейс начнёт отображать некорректные подсказки.
Решение заключается в введении идентификатора запроса:
let requestId = 0;
new Awesomplete(input, {
minChars: 2,
list: function (text, callback) {
const currentId = ++requestId;
fetch(`/api/cities?q=${encodeURIComponent(text)}`)
.then(res => res.json())
.then(data => {
if (currentId !== requestId) return;
callback(data);
});
}
});
Каждый новый ввод увеличивает requestId, и только
последний ответ считается актуальным. Это предотвращает гонки данных и
визуальные артефакты.
Для повышения производительности часто применяется кэширование результатов запросов. Это особенно эффективно при повторяющихся поисковых запросах.
const cache = new Map();
function fetchCities(query) {
if (cache.has(query)) {
return Promise.resolve(cache.get(query));
}
return fetch(`/api/cities?q=${encodeURIComponent(query)}`)
.then(res => res.json())
.then(data => {
cache.set(query, data);
return data;
});
}
Интеграция с Awesomplete:
new Awesomplete(input, {
minChars: 2,
list: function (text, callback) {
fetchCities(text).then(callback);
}
});
Кэширование снижает нагрузку на сервер и уменьшает задержки при повторных запросах.
При работе с большими наборами данных сервер часто возвращает результаты постранично. В этом случае асинхронная загрузка должна учитывать номер страницы и лимит элементов.
function fetchCities(query, page = 1) {
return fetch(`/api/cities?q=${encodeURIComponent(query)}&page=${page}`)
.then(res => res.json());
}
Расширенная логика:
new Awesomplete(input, {
minChars: 2,
list: function (text, callback) {
fetchCities(text, 1).then(response => {
callback(response.items);
});
}
});
При необходимости можно добавлять кнопку «показать ещё», но это уже выходит за рамки стандартного поведения автодополнения и требует кастомного UI.
При нестабильной сети или ошибках API необходимо предусмотреть безопасное поведение, чтобы интерфейс не ломался.
new Awesomplete(input, {
minChars: 2,
list: function (text, callback) {
fetch(`/api/cities?q=${encodeURIComponent(text)}`)
.then(res => {
if (!res.ok) throw new Error("Network error");
return res.json();
})
.then(data => callback(data))
.catch(() => callback([]));
}
});
Возврат пустого массива гарантирует, что интерфейс не будет отображать устаревшие или повреждённые данные.
Гибридная модель часто оказывается наиболее эффективной: часть данных хранится локально, а недостающие элементы подтягиваются с сервера.
const localCities = ["Almaty", "Astana", "Shymkent"];
new Awesomplete(input, {
minChars: 1,
list: function (text, callback) {
const filteredLocal = localCities.filter(item =>
item.toLowerCase().includes(text.toLowerCase())
);
if (filteredLocal.length >= 5) {
callback(filteredLocal);
return;
}
fetch(`/api/cities?q=${encodeURIComponent(text)}`)
.then(res => res.json())
.then(remote => {
callback([...filteredLocal, ...remote]);
});
}
});
Такая стратегия уменьшает задержки для популярных значений и сохраняет полноту результатов за счёт серверной части.
При работе с серверной подсказкой важно минимизировать объём передаваемых данных. Один из методов — отправка только последних символов или использование префиксного поиска.
function optimizeQuery(text) {
return text.trim().toLowerCase();
}
Применение:
list: function (text, callback) {
const q = optimizeQuery(text);
fetch(`/api/cities?q=${encodeURIComponent(q)}`)
.then(res => res.json())
.then(callback);
}
Такая нормализация позволяет серверу использовать индексы и ускоряет поиск.
Даже при быстрой сети резкие обновления списка могут создавать визуальный шум. Добавление минимальной задержки перед отображением улучшает UX.
function delayedCallback(callback, delay = 100) {
return function (data) {
setTimeout(() => callback(data), delay);
};
}
Использование:
list: function (text, callback) {
fetch(`/api/cities?q=${encodeURIComponent(text)}`)
.then(res => res.json())
.then(delayedCallback(callback));
}
Это сглаживает поведение интерфейса и делает обновление списка более естественным.
Современный подход к асинхронной загрузке предполагает возможность отмены устаревших запросов.
let controller;
new Awesomplete(input, {
minChars: 2,
list: function (text, callback) {
if (controller) controller.abort();
controller = new AbortController();
fetch(`/api/cities?q=${encodeURIComponent(text)}`, {
signal: controller.signal
})
.then(res => res.json())
.then(callback)
.catch(err => {
if (err.name !== "AbortError") {
callback([]);
}
});
}
});
Этот механизм предотвращает обработку ненужных ответов и снижает нагрузку на клиент.
Асинхронная загрузка в Awesomplete требует строгого контроля состояния: актуальность запроса, кэширование, обработка ошибок и управление частотой вызовов. Без этих механизмов автодополнение превращается в источник нестабильного поведения интерфейса, особенно при высокой скорости ввода.
Корректно построенная система асинхронных подсказок обеспечивает предсказуемость, минимальную задержку и масштабируемость при работе с большими объёмами данных.