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

Как выполнить два запроса параллельно?

1.0 Junior🔥 71 комментариев
#Браузер и сетевые технологии

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

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

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

Как выполнить два запроса параллельно в JavaScript

Параллельное выполнение запросов — это основной паттерн в веб-разработке. Вместо того чтобы ждать первого запроса, а потом делать второй, мы запускаем оба одновременно.

Основной подход: Promise.all()

Promise.all() — это самый распространённый способ:

// Запускаем оба запроса одновременно
const [users, posts] = await Promise.all([
  fetch("/api/users").then(r => r.json()),
  fetch("/api/posts").then(r => r.json())
]);

console.log(users);
console.log(posts);
// Оба запроса выполняются параллельно!
// Время выполнения = max(время1, время2), а не время1 + время2

Как это работает:

// Без параллелизма — медленно (5 сек + 3 сек = 8 сек)
async function sequential() {
  const users = await fetch("/api/users").then(r => r.json()); // 5 сек
  const posts = await fetch("/api/posts").then(r => r.json()); // 3 сек
  return { users, posts };
}

// С параллелизмом — быстро (max(5 сек, 3 сек) = 5 сек)
async function parallel() {
  const [users, posts] = await Promise.all([
    fetch("/api/users").then(r => r.json()),  // 5 сек
    fetch("/api/posts").then(r => r.json())   // 3 сек
  ]);
  return { users, posts };
}

Вариант 1: Promise.all() — все или ничего

Promise.all() отклоняет промис, если любой из запросов ошибается:

async function fetchData() {
  try {
    const [users, posts] = await Promise.all([
      fetch("/api/users").then(r => r.json()),
      fetch("/api/posts").then(r => r.json())
    ]);
    console.log("Success:", users, posts);
  } catch (error) {
    console.error("Error:", error);
    // Если один из запросов ошибся — ловим здесь
  }
}

Вариант 2: Promise.allSettled() — все запросы

Promise.allSettled() ждёт всех запросов, даже если они ошибаются:

async function fetchDataSafely() {
  const results = await Promise.allSettled([
    fetch("/api/users").then(r => r.json()),
    fetch("/api/posts").then(r => r.json())
  ]);
  
  // results = [
  //   { status: "fulfilled", value: [...users] },
  //   { status: "rejected", reason: Error(...) }
  // ]
  
  const [usersResult, postsResult] = results;
  
  if (usersResult.status === "fulfilled") {
    console.log("Users:", usersResult.value);
  } else {
    console.error("Users error:", usersResult.reason);
  }
  
  if (postsResult.status === "fulfilled") {
    console.log("Posts:", postsResult.value);
  } else {
    console.error("Posts error:", postsResult.reason);
  }
}

Вариант 3: Promise.race() — первый результат

Promise.race() возвращает результат первого выполненного промиса:

// Подождать первого успешного ответа
const firstResponse = await Promise.race([
  fetch("/api/fast-server-1").then(r => r.json()),
  fetch("/api/fast-server-2").then(r => r.json()),
  fetch("/api/fast-server-3").then(r => r.json())
]);

// Ис пользование: таймаут для запроса
function fetchWithTimeout(url, timeout = 5000) {
  return Promise.race([
    fetch(url),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error("Timeout")), timeout)
    )
  ]);
}

await fetchWithTimeout("/api/slow", 3000); // Либо ответ, либо Error через 3 сек

Практический пример: загрузка данных профиля

// Профиль состоит из нескольких кусков данных
async function loadUserProfile(userId) {
  try {
    const [profile, settings, followers] = await Promise.all([
      fetch(`/api/users/${userId}`).then(r => r.json()),
      fetch(`/api/users/${userId}/settings`).then(r => r.json()),
      fetch(`/api/users/${userId}/followers`).then(r => r.json())
    ]);
    
    return { profile, settings, followers };
  } catch (error) {
    console.error("Failed to load profile", error);
    throw error;
  }
}

// Использование
const userProfile = await loadUserProfile(123);

Объединение параллельных запросов с серией

Иногда нужна комбинация:

async function complexDataFlow() {
  // 1. Сначала загружаем пользователя
  const user = await fetch("/api/me").then(r => r.json());
  
  // 2. Затем параллельно загружаем его посты и комментарии
  const [posts, comments] = await Promise.all([
    fetch(`/api/users/${user.id}/posts`).then(r => r.json()),
    fetch(`/api/users/${user.id}/comments`).then(r => r.json())
  ]);
  
  // 3. Наконец загружаем авторов каждого комментария параллельно
  const authors = await Promise.all(
    comments.map(comment =>
      fetch(`/api/users/${comment.authorId}`).then(r => r.json())
    )
  );
  
  return { user, posts, comments, authors };
}

С использованием async/await и Promise.all()

// Чистый async/await синтаксис
async function getTwoThings() {
  // Запускаем оба promise одновременно
  const promise1 = fetch("/api/data1").then(r => r.json());
  const promise2 = fetch("/api/data2").then(r => r.json());
  
  // Ждём оба
  const [data1, data2] = await Promise.all([promise1, promise2]);
  
  return { data1, data2 };
}

// Или более компактно
async function getTwoThingsCompact() {
  return Promise.all([
    fetch("/api/data1").then(r => r.json()),
    fetch("/api/data2").then(r => r.json())
  ]);
}

Обработка ошибок с allSettled

async function robustFetch() {
  const results = await Promise.allSettled([
    fetch("/api/critical").then(r => r.json()),
    fetch("/api/optional1").then(r => r.json()),
    fetch("/api/optional2").then(r => r.json())
  ]);
  
  const [critical, optional1, optional2] = results;
  
  // Проверяем критичный запрос
  if (critical.status === "rejected") {
    throw new Error("Critical data failed to load");
  }
  
  // Опциональные могут быть пропущены
  const data1 = optional1.status === "fulfilled" ? optional1.value : null;
  const data2 = optional2.status === "fulfilled" ? optional2.value : null;
  
  return {
    critical: critical.value,
    data1,
    data2
  };
}

Ограничение параллелизма

Иногда нельзя запускать ВСЕ запросы сразу (ограничение API):

// Максимум 3 параллельных запроса
async function fetchWithLimit(urls, limit = 3) {
  const results = [];
  
  for (let i = 0; i < urls.length; i += limit) {
    const batch = urls.slice(i, i + limit);
    const batchResults = await Promise.all(
      batch.map(url => fetch(url).then(r => r.json()))
    );
    results.push(...batchResults);
  }
  
  return results;
}

const data = await fetchWithLimit(
  ["/api/1", "/api/2", "/api/3", "/api/4", "/api/5"],
  2  // По 2 запроса за раз
);

В React компонентах

import { useEffect, useState } from "react";

function UserProfile({ userId }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    // Запускаем параллельные запросы
    Promise.all([
      fetch(`/api/users/${userId}`).then(r => r.json()),
      fetch(`/api/users/${userId}/posts`).then(r => r.json())
    ])
      .then(([user, posts]) => {
        setData({ user, posts });
        setLoading(false);
      })
      .catch(err => {
        setError(err);
        setLoading(false);
      });
  }, [userId]);
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <div>
      <h1>{data.user.name}</h1>
      <p>Posts: {data.posts.length}</p>
    </div>
  );
}

Таблица сравнения методов

МетодПоведение при ошибкеИспользование
Promise.all()Отклоняет сразуКогда все запросы критичны
Promise.allSettled()Ждёт всехКогда некоторые могут ошибаться
Promise.race()Первый результатТаймауты, балансировка

Производительность

Параллельные запросы экономят время:

Последовательно:
fetch1 (5s) -> fetch2 (3s) = 8 секунд

Параллельно:
fetch1 (5s) |
fetch2 (3s) | = 5 секунд (max)

Экономия времени: 37.5%

Всегда используй Promise.all() для независимых запросов!