JavaScript · API · Продвинутый

Параллельные запросы с Promise.all

5 задач

Используйте Promise.all для параллельных запросов API и ускорения скриптов.

Асинхронные запросы и Promise.all

#
По умолчанию `await` выполняет запросы по одному — каждый ждёт, пока завершится предыдущий: ``` fetch товар 1 → ждать → fetch товар 2 → ждать → ... ``` С `Promise.all` несколько запросов запускаются одновременно: ``` fetch товар 1 ┐ fetch товар 2 ├→ все запускаются параллельно → ждать всех fetch товар 3 ┘ ``` Базовое использование — передайте массив Promise, получите массив результатов: ```javascript const [products, categories] = await Promise.all([ fetch('https://apilearn.tukas.dev/api/products/').then(r => r.json()), fetch('https://apilearn.tukas.dev/api/categories/').then(r => r.json()), ]); console.log(products.count, categories.length); ``` Результаты возвращаются в том же порядке, что и входной массив — `products` всегда индекс 0, `categories` всегда индекс 1. `Promise.all` сразу отклоняется, если **любой** Promise отклоняется. Если нужно обрабатывать частичные сбои, используйте `Promise.allSettled`: ```javascript const results = await Promise.allSettled([ fetch(url1).then(r => r.json()), fetch(url2).then(r => r.json()), ]); for (const result of results) { if (result.status === 'fulfilled') console.log(result.value); else console.log('Сбой:', result.reason); } ```

Параллельные запросы с Promise.all

#
Получаем товары и категории одновременно: ```javascript const BASE_URL = 'https://apilearn.tukas.dev'; async function main() { const [productsData, categories] = await Promise.all([ fetch(`${BASE_URL}/api/products/`).then(r => r.json()), fetch(`${BASE_URL}/api/categories/`).then(r => r.json()), ]); console.log('Всего товаров:', productsData.count); console.log('Категорий:', categories.length); } main(); ``` Получаем несколько товаров по id параллельно: ```javascript async function fetchProduct(id) { return fetch(`${BASE_URL}/api/products/${id}/`).then(r => r.json()); } async function main() { // Получаем товары 1-5 одновременно const products = await Promise.all( [1, 2, 3, 4, 5].map(id => fetchProduct(id)) ); for (const p of products) { console.log(p.name, p.price); } } main(); ``` Загружаем несколько источников данных одновременно для дашборда: ```javascript async function loadDashboard(token) { const headers = { 'Authorization': `Token ${token}` }; const [productsData, categories, profile, cart] = await Promise.all([ fetch(`${BASE_URL}/api/products/`).then(r => r.json()), fetch(`${BASE_URL}/api/categories/`).then(r => r.json()), fetch(`${BASE_URL}/api/users/profile/`, { headers }).then(r => r.json()), fetch(`${BASE_URL}/api/cart/`, { headers }).then(r => r.json()), ]); console.log(`Добро пожаловать, ${profile.username}!`); console.log(`${productsData.count} товаров в ${categories.length} категориях`); console.log(`Корзина: ${cart.items.length} товаров`); } ```

Справочник Promise.all и параллельных запросов

#
**Promise.all — базовый** ```javascript const [a, b] = await Promise.all([promiseA, promiseB]); // результаты в том же порядке, что и входные // сразу отклоняется, если любой Promise отклоняется ``` **Паттерн map для динамических массивов** ```javascript const ids = [1, 2, 3, 4, 5]; const products = await Promise.all(ids.map(id => fetch(`${BASE_URL}/api/products/${id}/`).then(r => r.json()) )); ``` **Promise.allSettled — частичные сбои** ```javascript const results = await Promise.allSettled([p1, p2, p3]); const succeeded = results.filter(r => r.status === 'fulfilled').map(r => r.value); const failed = results.filter(r => r.status === 'rejected').map(r => r.reason); ``` **Последовательно vs параллельно** ```javascript // Последовательно — медленно const a = await fetchA(); const b = await fetchB(); // ждёт A // Параллельно — быстро const [a, b] = await Promise.all([fetchA(), fetchB()]); ``` **Когда использовать Promise.all** ``` Несколько независимых запросов → используйте Promise.all Запросы зависят друг от друга → последовательный await Любой сбой останавливает всё → Promise.all Обрабатывать сбои отдельно → Promise.allSettled ```
01

Первый Promise.all

#

Используйте `Promise.all` для одновременного получения `/api/products/` и `/api/categories/`. Выведите общее количество товаров и количество категорий.

const BASE_URL = 'https://apilearn.tukas.dev';

async function main() {
  // получить товары и категории параллельно через Promise.all
}

main();
Решение
const BASE_URL = 'https://apilearn.tukas.dev';

async function main() {
  const [productsData, categories] = await Promise.all([
    fetch(`${BASE_URL}/api/products/`).then(r => r.json()),
    fetch(`${BASE_URL}/api/categories/`).then(r => r.json()),
  ]);
  console.log(productsData.count);
  console.log(categories.length);
}

main();
02

Получить несколько товаров параллельно

#

Получите товары с id 1, 2, 3, 4, 5 одновременно с помощью `Promise.all` и `.map()`. Выведите название и цену каждого.

const BASE_URL = 'https://apilearn.tukas.dev';

async function main() {
  // получить 5 товаров параллельно с помощью .map() и Promise.all
}

main();
Решение
const BASE_URL = 'https://apilearn.tukas.dev';

async function main() {
  const products = await Promise.all(
    [1, 2, 3, 4, 5].map(id =>
      fetch(`${BASE_URL}/api/products/${id}/`).then(r => r.json())
    )
  );
  for (const p of products) {
    console.log(p.name, p.price);
  }
}

main();
03

Авторизованные параллельные запросы

#

Получите токен, затем используйте `Promise.all` для одновременного получения профиля и корзины. Выведите `username` и количество товаров в корзине.

const BASE_URL = 'https://apilearn.tukas.dev';

async function main() {
  const { token } = await fetch(`${BASE_URL}/api/auth/token/`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username: 'YOUR_USERNAME', password: 'YOUR_PASSWORD' }),
  }).then(r => r.json());

  const headers = { 'Authorization': `Token ${token}` };

  // получить профиль и корзину параллельно
}

main();
Решение
const BASE_URL = 'https://apilearn.tukas.dev';

async function main() {
  const { token } = await fetch(`${BASE_URL}/api/auth/token/`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username: 'YOUR_USERNAME', password: 'YOUR_PASSWORD' }),
  }).then(r => r.json());

  const headers = { 'Authorization': `Token ${token}` };

  const [profile, cart] = await Promise.all([
    fetch(`${BASE_URL}/api/users/profile/`, { headers }).then(r => r.json()),
    fetch(`${BASE_URL}/api/cart/`, { headers }).then(r => r.json()),
  ]);

  console.log(profile.username);
  console.log(cart.items.length);
}

main();
04

Сравнить последовательный и параллельный подходы

#

Получите товары с id 1–5 дважды: один раз последовательно через цикл, один раз параллельно через `Promise.all`. Измерьте и выведите время для каждого подхода с помощью `Date.now()`.

const BASE_URL = 'https://apilearn.tukas.dev';

async function main() {
  // последовательно
  let start = Date.now();
  // ... цикл ...
  console.log('Последовательно:', Date.now() - start, 'мс');

  // параллельно
  start = Date.now();
  // ... Promise.all ...
  console.log('Параллельно:', Date.now() - start, 'мс');
}

main();
Решение
const BASE_URL = 'https://apilearn.tukas.dev';

async function main() {
  // Sequential
  let start = Date.now();
  for (const id of [1, 2, 3, 4, 5]) {
    await fetch(`${BASE_URL}/api/products/${id}/`).then(r => r.json());
  }
  console.log('Sequential:', Date.now() - start, 'ms');

  // Parallel
  start = Date.now();
  await Promise.all(
    [1, 2, 3, 4, 5].map(id =>
      fetch(`${BASE_URL}/api/products/${id}/`).then(r => r.json())
    )
  );
  console.log('Parallel:', Date.now() - start, 'ms');
}

main();
05

Загрузчик дашборда с Promise.all

#

Напишите функцию `loadDashboard(token)`, которая с помощью `Promise.all` одновременно получает товары, категории, профиль и корзину. Выведите сводку: username, количество товаров, количество категорий, количество товаров в корзине.

const BASE_URL = 'https://apilearn.tukas.dev';

async function loadDashboard(token) {
  // получить 4 ресурса параллельно, вывести сводку
}

async function main() {
  const { token } = await fetch(`${BASE_URL}/api/auth/token/`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username: 'YOUR_USERNAME', password: 'YOUR_PASSWORD' }),
  }).then(r => r.json());

  await loadDashboard(token);
}

main();
Решение
const BASE_URL = 'https://apilearn.tukas.dev';

async function loadDashboard(token) {
  const headers = { 'Authorization': `Token ${token}` };

  const [productsData, categories, profile, cart] = await Promise.all([
    fetch(`${BASE_URL}/api/products/`).then(r => r.json()),
    fetch(`${BASE_URL}/api/categories/`).then(r => r.json()),
    fetch(`${BASE_URL}/api/users/profile/`, { headers }).then(r => r.json()),
    fetch(`${BASE_URL}/api/cart/`, { headers }).then(r => r.json()),
  ]);

  console.log(`Welcome, ${profile.username}!`);
  console.log(`${productsData.count} products in ${categories.length} categories`);
  console.log(`Cart: ${cart.items.length} items`);
}

async function main() {
  const { token } = await fetch(`${BASE_URL}/api/auth/token/`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username: 'YOUR_USERNAME', password: 'YOUR_PASSWORD' }),
  }).then(r => r.json());

  await loadDashboard(token);
}

main();