При работе с автодополнением в реальных интерфейсах статические списки практически всегда заменяются динамическими источниками данных. Awesomplete в таком сценарии выступает как слой отображения и управления подсказками, тогда как получение и подготовка данных переносится во внешние API или внутренние сервисы.
Ключевая особенность архитектуры заключается в разделении ответственности:
Типовой сценарий основан на использовании fetch для
обращения к REST API. Запрос формируется на основе текущего значения
input-поля, которое отслеживается через событие input.
const input = document.querySelector("#search");
const awesomplete = new Awesomplete(input, {
minChars: 2,
maxItems: 10
});
Далее подключается обработка ввода:
input.addEventListener("input", async () => {
const query = input.value.trim();
if (query.length < 2) {
awesomplete.list = [];
return;
}
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
const data = await response.json();
awesomplete.list = data.results;
});
На этом уровне система уже функционирует, но остаётся ряд проблем: избыточные запросы, отсутствие кэширования, гонки ответов и нестабильность при медленных сетях.
При каждом вводе символа возникает событие input, что
приводит к множеству запросов. Для уменьшения нагрузки используется
механизм debounce.
function debounce(fn, delay) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
Интеграция с Awesomplete:
const fetchSuggestions = debounce(async (query) => {
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
const data = await response.json();
awesomplete.list = data.results;
}, 300);
input.addEventListener("input", () => {
const query = input.value.trim();
if (query.length < 2) {
awesomplete.list = [];
return;
}
fetchSuggestions(query);
});
Дебаунсинг снижает количество запросов и стабилизирует поведение интерфейса при быстром вводе.
При асинхронной загрузке данных возможна ситуация, когда более ранний запрос возвращается позже нового, перезаписывая актуальные данные.
Решение основано на использовании идентификатора запроса:
let requestId = 0;
const fetchSuggestions = debounce(async (query) => {
const currentId = ++requestId;
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
const data = await response.json();
if (currentId !== requestId) return;
awesomplete.list = data.results;
}, 300);
Такой подход гарантирует, что в интерфейс попадают только актуальные результаты.
Для повторяющихся запросов используется кэш на стороне клиента. Это уменьшает задержки и снижает нагрузку на API.
const cache = new Map();
async function getSuggestions(query) {
if (cache.has(query)) {
return cache.get(query);
}
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
const data = await response.json();
cache.set(query, data.results);
return data.results;
}
Интеграция с Awesomplete:
const fetchSuggestions = debounce(async (query) => {
const results = await getSuggestions(query);
awesomplete.list = results;
}, 300);
В продвинутых сценариях кэш дополняется TTL-логикой, ограничивающей время жизни записей.
Внешние API часто возвращают сложные объекты, тогда как Awesomplete
ожидает массив строк или объектов с полями label и
value.
Пример нормализации:
function normalize(data) {
return data.map(item => ({
label: item.title,
value: item.id
}));
}
Использование:
const data = await response.json();
awesomplete.list = normalize(data.results);
При выборе сложных объектов важно учитывать, что Awesomplete
отображает label, а в input подставляет
value.
При нестабильной сети или ошибках API необходимо предотвращать поломку интерфейса.
async function getSuggestions(query) {
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
if (!response.ok) {
throw new Error("API error");
}
const data = await response.json();
return data.results;
} catch (error) {
return [];
}
}
Дополнительно может использоваться fallback на локальные данные:
const fallback = ["JavaScript", "Java", "Python", "PHP"];
Некоторые API возвращают данные постранично. В этом случае подсказки дополняются динамически.
let page = 1;
let currentQuery = "";
async function loadPage(query, page) {
const response = await fetch(`/api/search?q=${query}&page=${page}`);
return response.json();
}
При необходимости расширения списка:
const data = await loadPage(currentQuery, page);
awesomplete.list = [
...awesomplete.list,
...data.results
];
page++;
Такой подход используется для сценариев с бесконечной выдачей подсказок.
Помимо debounce часто применяется rate limiting на клиенте, особенно при работе с дорогими API.
let lastCall = 0;
function rateLimit(fn, limit) {
return (...args) => {
const now = Date.now();
if (now - lastCall < limit) return;
lastCall = now;
fn(...args);
};
}
Комбинация debounce и rate limit снижает риск перегрузки сервера и блокировки ключей API.
В браузерной среде запросы к внешним API ограничиваются политикой CORS. При отсутствии разрешающих заголовков используется серверный прокси.
Клиентская часть:
fetch(`/proxy/search?q=${encodeURIComponent(query)}`)
Серверная часть (пример на Node.js):
app.get("/proxy/search", async (req, res) => {
const query = req.query.q;
const response = await fetch(`https://external-api.com/search?q=${query}`);
const data = await response.json();
res.json(data);
});
Проксирование позволяет централизовать кэширование, логирование и контроль доступа.
const input = document.querySelector("#search");
const awesomplete = new Awesomplete(input, {
minChars: 2,
maxItems: 10
});
const cache = new Map();
let requestId = 0;
async function getSuggestions(query) {
if (cache.has(query)) return cache.get(query);
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
const data = await response.json();
cache.set(query, data.results);
return data.results;
}
const fetchSuggestions = debounce(async (query) => {
const currentId = ++requestId;
const results = await getSuggestions(query);
if (currentId !== requestId) return;
awesomplete.list = results.map(item => ({
label: item.title,
value: item.id
}));
}, 300);
input.addEventListener("input", () => {
const query = input.value.trim();
if (query.length < 2) {
awesomplete.list = [];
return;
}
fetchSuggestions(query);
});
Такая структура объединяет асинхронную загрузку, защиту от гонок, кэширование и нормализацию данных, сохраняя Awesomplete в роли лёгкого UI-слоя для отображения подсказок.