Есть ли в браузере ограничение по количеству параллельных запросов?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Ограничения браузера на параллельные запросы
Да, у браузеров есть ограничения на количество одновременных (параллельных) 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 и наблюдай за загрузкой
Заключение
Основные моменты:
- Браузеры ограничивают до 6-8 параллельных запросов на домен
- Это приводит к очередям при множественных запросах
- HTTP/2 решает это через multiplexing
- Оптимизация: объединяй запросы, используй GraphQL, кэшируй, приоритизируй
- В production убедись, что сервер поддерживает HTTP/2
Это важный момент для оптимизации production приложений!