← Назад к вопросам
Как оптимизировать отправку поисковых запросов на сервер?
1.8 Middle🔥 161 комментариев
#JavaScript Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Оптимизация поисковых запросов на сервер
Поиск на фронтенде часто выполняется на каждый ввод символа. Без оптимизации это создает огромную нагрузку на сервер и замедляет приложение. Существует несколько проверенных техник.
Debounce (Антидребезг)
Задержка отправки запроса до момента, когда пользователь прекратит вводить текст:
function debounce(fn, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
};
}
const searchUsers = debounce(async (query) => {
const response = await fetch(`/api/users/search?q=${query}`);
const results = await response.json();
displayResults(results);
}, 300); // Ждем 300ms после последнего ввода
input.addEventListener('input', (e) => {
searchUsers(e.target.value);
});
Throttle (Дроссель)
Отправлять запросы не чаще, чем раз в N миллисекунд:
function throttle(fn, delay) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall > delay) {
lastCall = now;
fn(...args);
}
};
}
const searchOptimized = throttle(async (query) => {
const response = await fetch(`/api/users/search?q=${query}`);
const results = await response.json();
displayResults(results);
}, 500); // Максимум раз в 500ms
input.addEventListener('input', (e) => {
searchOptimized(e.target.value);
});
AbortController для отмены запросов
Отмени предыдущие запросы при новом поиске:
let abortController = null;
async function search(query) {
// Отмени предыдущий запрос
if (abortController) {
abortController.abort();
}
abortController = new AbortController();
try {
const response = await fetch(
`/api/users/search?q=${query}`,
{ signal: abortController.signal }
);
const results = await response.json();
displayResults(results);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Ошибка поиска:', error);
}
}
}
const debouncedSearch = debounce(search, 300);
input.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
Кэширование результатов
Не отправляй запрос, если уже есть результат для этого поискового запроса:
class SearchCache {
constructor(ttl = 5000) { // 5 секунд
this.cache = new Map();
this.ttl = ttl;
}
set(key, value) {
const entry = {
value,
timestamp: Date.now()
};
this.cache.set(key, entry);
}
get(key) {
const entry = this.cache.get(key);
if (!entry) return null;
const isExpired = Date.now() - entry.timestamp > this.ttl;
if (isExpired) {
this.cache.delete(key);
return null;
}
return entry.value;
}
}
const cache = new SearchCache(5000);
async function search(query) {
if (!query.trim()) return;
// Проверь кэш
const cached = cache.get(query);
if (cached) {
displayResults(cached);
return;
}
try {
const response = await fetch(`/api/users/search?q=${query}`);
const results = await response.json();
cache.set(query, results);
displayResults(results);
} catch (error) {
console.error('Ошибка поиска:', error);
}
}
const debouncedSearch = debounce(search, 300);
input.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
Минимальная длина запроса
Не отправляй запрос, пока пользователь не введет достаточно символов:
const MIN_QUERY_LENGTH = 3;
async function search(query) {
if (query.trim().length < MIN_QUERY_LENGTH) {
clearResults();
return;
}
try {
const response = await fetch(`/api/users/search?q=${query}`);
const results = await response.json();
displayResults(results);
} catch (error) {
console.error('Ошибка поиска:', error);
}
}
const debouncedSearch = debounce(search, 300);
input.addEventListener('input', (e) => {
debouncedSearch(e.target.value);
});
React хук для поиска
Полный пример с React::
import { useState, useCallback, useRef } from 'react';
function useSearch() {
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const abortControllerRef = useRef(null);
const search = useCallback(async (query) => {
if (query.trim().length < 3) {
setResults([]);
return;
}
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
abortControllerRef.current = new AbortController();
setIsLoading(true);
try {
const response = await fetch(
`/api/search?q=${encodeURIComponent(query)}`,
{ signal: abortControllerRef.current.signal }
);
const data = await response.json();
setResults(data);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Search error:', error);
}
} finally {
setIsLoading(false);
}
}, []);
return { results, isLoading, search };
}
Комбинированный подход (Рекомендуется)
Сочетай несколько техник для максимальной эффективности:
const cache = new SearchCache(10000);
let abortController = null;
const search = debounce(async (query) => {
if (query.trim().length < 2) {
clearResults();
return;
}
// Проверь кэш
const cached = cache.get(query);
if (cached) {
displayResults(cached);
return;
}
// Отмени старый запрос
if (abortController) {
abortController.abort();
}
abortController = new AbortController();
try {
const response = await fetch(
`/api/search?q=${encodeURIComponent(query)}`,
{ signal: abortController.signal }
);
const results = await response.json();
cache.set(query, results);
displayResults(results);
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Search error:', error);
}
}
}, 300);
input.addEventListener('input', (e) => {
search(e.target.value);
});
Этот подход сокращает нагрузку на сервер в 10-100 раз, улучшает UX и экономит трафик.