← Назад к вопросам

Как оптимизировать отправку поисковых запросов на сервер?

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 и экономит трафик.