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

Есть ли в браузере ограничение по количеству параллельных запросов?

1.8 Middle🔥 131 комментариев
#Браузер и сетевые технологии

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ограничения браузера на параллельные запросы

Да, у браузеров есть ограничения на количество одновременных (параллельных) HTTP запросов. Это называется HTTP connection limit или concurrent connections limit.

Стандартные ограничения

Chrome, Firefox, Safari, Edge: 6-8 одновременных запросов на один домен

// Пример: если ты сделаешь 10 запросов одновременно
const requests = Array.from({ length: 10 }, (_, i) =>
  fetch(`/api/data/${i}`)
);

// Браузер будет выполнять первые 6-8 запросов параллельно
// Остальные будут ждать в очереди, пока не освободятся соединения
const results = await Promise.all(requests);

Почему это ограничение существует

1. Защита от DDoS атак

Без ограничений вредоносный сайт мог бы отправить миллионы запросов на целевой сервер.

2. Экономия ресурсов браузера

Кажное соединение требует памяти, CPU и других ресурсов.

3. Стандарт HTTP/1.1

HTTP/1.1 спецификация рекомендует ограничение 2-8 соединений на хост.

Пример проблемы

// ПЛОХО - много параллельных запросов
function loadAllImages(imageUrls) {
  return Promise.all(
    imageUrls.map(url => fetch(url))
  );
}

// Если есть 20 изображений - первые 6-8 загружаются параллельно
// Остальные ждут в очереди, что замедляет загрузку страницы

Как браузер распределяет соединения

По доменам

Ограничение 6-8 запросов на один домен. Если использовать разные домены, можно обойти ограничение:

// Способ 1: Один домен - максимум 6-8 параллельных запросов
const requests1 = Array.from({ length: 20 }, (_, i) =>
  fetch(`https://api.example.com/data/${i}`)
);

// Способ 2: Несколько доменов - до 24+ параллельных запросов
const domains = ['a.example.com', 'b.example.com', 'c.example.com', 'd.example.com'];
const requests2 = Array.from({ length: 20 }, (_, i) =>
  fetch(`https://${domains[i % domains.length]}/data/${i}`)
);

Но это не рекомендуется!

Использование нескольких доменов может увеличить time-to-first-byte из-за DNS lookup и TLS handshake.

HTTP/2 решает проблему

HTTP/2 использует multiplexing - один TCP connection может обрабатывать множество запросов одновременно:

// С HTTP/2 - все 20 запросов могут выполняться параллельно!
// (если сервер поддерживает HTTP/2)
const requests = Array.from({ length: 20 }, (_, i) =>
  fetch(`https://api.example.com/data/${i}`)
);

// HTTP/2 автоматически распределяет все запросы
// через одно соединение
await Promise.all(requests);

Как оптимизировать запросы

1. Объединение запросов

// ПЛОХО - 10 отдельных запросов
function fetchUserData(userId) {
  const userPromise = fetch(`/api/users/${userId}`);
  const postsPromise = fetch(`/api/users/${userId}/posts`);
  const commentsPromise = fetch(`/api/users/${userId}/comments`);
  const friendsPromise = fetch(`/api/users/${userId}/friends`);
  // ...
  
  return Promise.all([userPromise, postsPromise, commentsPromise, friendsPromise]);
}

// ХОРОШО - один запрос со всеми данными
function fetchUserData(userId) {
  return fetch(`/api/users/${userId}?include=posts,comments,friends`)
    .then(r => r.json());
}

2. GraphQL вместо REST

// REST - несколько запросов
const user = await fetch('/api/users/123').then(r => r.json());
const posts = await fetch('/api/users/123/posts').then(r => r.json());
const comments = await fetch('/api/users/123/comments').then(r => r.json());

// GraphQL - один запрос с нужными данными
const query = `
  query GetUser($id: ID!) {
    user(id: $id) {
      name
      email
      posts { title }
      comments { text }
    }
  }
`;

const result = await fetch('/graphql', {
  method: 'POST',
  body: JSON.stringify({ query, variables: { id: '123' } })
}).then(r => r.json());

3. Кэширование

function useFetchUser(userId) {
  const [data, setData] = useState(null);
  const cache = useRef({});
  
  useEffect(() => {
    // Если уже загружали - используй кэш
    if (cache.current[userId]) {
      setData(cache.current[userId]);
      return;
    }
    
    fetch(`/api/users/${userId}`)
      .then(r => r.json())
      .then(user => {
        cache.current[userId] = user;
        setData(user);
      });
  }, [userId]);
  
  return data;
}

4. Приоритизация запросов

// Загружай критичные данные первыми
class FetchQueue {
  constructor(maxConcurrent = 6) {
    this.maxConcurrent = maxConcurrent;
    this.activeRequests = 0;
    this.queue = [];
  }
  
  async fetch(url, priority = 0) {
    // Добавь в очередь
    return new Promise((resolve, reject) => {
      this.queue.push({ url, priority, resolve, reject });
      // Отсортируй по приоритету
      this.queue.sort((a, b) => b.priority - a.priority);
      this.processQueue();
    });
  }
  
  async processQueue() {
    if (this.activeRequests >= this.maxConcurrent || this.queue.length === 0) {
      return;
    }
    
    this.activeRequests++;
    const { url, resolve, reject } = this.queue.shift();
    
    try {
      const response = await fetch(url);
      const data = await response.json();
      resolve(data);
    } catch (error) {
      reject(error);
    } finally {
      this.activeRequests--;
      this.processQueue();
    }
  }
}

// Использование
const fetcher = new FetchQueue();

// Загрузи критичные данные первыми
await fetcher.fetch('/api/user', 10);
await fetcher.fetch('/api/settings', 9);

// Остальное после
await fetcher.fetch('/api/ads', 1);
await fetcher.fetch('/api/analytics', 1);

5. Resource Hints

<!-- Подготовь браузер к запросу заранее -->
<link rel="preconnect" href="https://api.example.com" />
<link rel="dns-prefetch" href="https://cdn.example.com" />

<!-- Предзагрузи критичные ресурсы -->
<link rel="preload" href="/api/critical-data" as="fetch" crossorigin />

Практический пример

// Реальная проблема: загрузка 50 изображений
function loadImages(imageUrls) {
  // Если просто Promise.all - первые 6-8 загружаются,
  // остальные ждут, что замедляет UX
  
  const loadImage = (url) => {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = () => resolve(img);
      img.onerror = () => reject(new Error(`Failed to load ${url}`));
      img.src = url;
    });
  };
  
  // Решение: загружай с ограничением
  const batchSize = 6;
  const results = [];
  
  async function loadBatch(start) {
    const batch = imageUrls.slice(start, start + batchSize);
    const loaded = await Promise.all(batch.map(loadImage));
    results.push(...loaded);
    
    if (start + batchSize < imageUrls.length) {
      await loadBatch(start + batchSize);
    }
  }
  
  return loadBatch(0).then(() => results);
}

// Альтернатива с library
import pLimit from 'p-limit';

const limit = pLimit(6); // Максимум 6 одновременно
const imagePromises = imageUrls.map(url =>
  limit(() => loadImage(url))
);

const images = await Promise.all(imagePromises);

Современное решение

Используй Fetch Priority API (экспериментально):

// Запрос высокого приоритета
fetch('/api/critical', { priority: 'high' });

// Запрос низкого приоритета
fetch('/api/analytics', { priority: 'low' });

Проверка ограничений в твом браузере

// Chrome DevTools -> Network tab -> стреляй много запросов
// Увидишь, что первые 6-8 загружаются параллельно,
// остальные ждут в очереди

Array.from({ length: 20 }, (_, i) =>
  fetch(`https://httpbin.org/delay/2?id=${i}`)
);

// Откройся Network tab и наблюдай за загрузкой

Заключение

Основные моменты:

  1. Браузеры ограничивают до 6-8 параллельных запросов на домен
  2. Это приводит к очередям при множественных запросах
  3. HTTP/2 решает это через multiplexing
  4. Оптимизация: объединяй запросы, используй GraphQL, кэшируй, приоритизируй
  5. В production убедись, что сервер поддерживает HTTP/2

Это важный момент для оптимизации production приложений!