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();