StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Doradztwo produktowe

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Doradztwo produktowe

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Doradztwo produktowe

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

RealizacjeO mnieBlog
Porozmawiajmy
PL
EN

Nowoczesne strony internetowe dla firm, które myślą odważnie.

Przewiń do góry

Nazwa

StriveLab Maciej Sala

NIP

6772218995

REGON

524008527

E-mail

contact@strivelab.pl

Usługi główne
  • Tworzenie stron internetowych
  • Strony internetowe Next.js
  • Strony internetowe Astro
  • Strony internetowe React
Inne usługi
  • Usługi
  • SEO & Performance Sprint
  • QA & Stabilizacja
  • Konsultacje Product / Delivery
  • Automatyzacja Procesów AI
  • Aplikacje webowe Next.js
  • Współpraca ciągła
Strony
  • O mnie
  • Usługi
  • Realizacje
  • Blog

© 2026 StriveLab.pl

Polityka prywatności
JavaScriptWydajność

Async/await to pułapka — kiedy Promise.all() uratuje Twoją wydajność

Sekwencyjne await potrafią niepotrzebnie wydłużyć czas odpowiedzi aplikacji. Zobacz, kiedy użyć Promise.all(), kiedy Promise.allSettled(), a kiedy ograniczyć równoległość.

OpublikujLinkedInFacebookWyślij
Autor
Maciej Sala
Opublikowano
2 listopada 2025 11:38
Czytanie
5 min czytania
Aktualizacja
25 maja 2026 10:55

async/await zrobił dla czytelności JavaScript więcej niż większość nowych składni razem wziętych. Po jego wprowadzeniu, kod wygląda liniowo, łatwiej go prześledzić i trudniej zgubić się w łańcuchu callbacków. Z drugiej strony problem w tym, że bardzo łatwo napisać kod, który wygląda dobrze, a jednocześnie niepotrzebnie serializuje pracę.

Artykuł w skrócie

  • Sekwencyjny await to antywzorzec — dwa niezależne await jeden pod drugim mogą podwoić czas odpowiedzi bez żadnego powodu
  • Promise.all() — równolegle uruchamia niezależne operacje; jeśli jedna rzuci błąd, cała grupa się nie powiedzie (fail-fast)
  • Promise.allSettled() — czeka na wszystkie i zbiera wyniki oraz błędy; wybierz to gdy brak jednego wyniku nie powinien blokować pozostałych
  • forEach + async to pułapka — callback wywoływany bez await na poziomie pętli; użyj Promise.all(arr.map(async...)) lub for...of
  • Batching dla dużych zbiorów — Promise.all na tysiąc requestów naraz może przeciążyć serwer; dziel na grupy po 10–50 elementów
  • Sekwencyjne await ma sens — zostaw je tylko wtedy, gdy wynik jednej operacji jest potrzebny do kolejnej

Samo await nie jest pułapką, tylko ustawienie kilku await jeden po drugim wtedy, gdy operacje są od siebie niezależne i mogłyby wystartować równolegle.

W praktyce ten błąd pojawia się w dashboardach, loaderach danych, SSR, czyli Server-Side Rendering, to generowanie HTML na serwerze przy żądaniu — komponent client:only je pomija i renderuje się wyłącznie w przeglądarce. i integracjach z kilkoma API, czyli Application Programming Interface, definiuje sposób komunikacji między aplikacjami lub modułami. Tu chodzi o konwencje plików Next.js, które zamieniają eksportowaną funkcję w gotowy plik sitemap.xml lub robots.txt.. Każdy request jest poprawny, ale całość trwa dłużej, niżeli musi. W tym artykule pokażę, jak to rozpoznać, kiedy Promise.all() faktycznie pomaga, a kiedy lepiej zostać przy sekwencji albo wprowadzić limit równoległości.

Uwaga

Promise.all() przyspiesza tylko operacje niezależne. Jeśli druga operacja potrzebuje wyniku pierwszej albo zewnętrzne API ma limity, równoległość bez kontroli może dać błędy, throttling albo trudniejsze debugowanie.

Problem: sekwencyjne await

Wyobraź sobie typową funkcję, która pobiera dane z kilku źródeł:

Code
async function getDashboardData(userId) {
  const user = await fetchUser(userId) // 200ms
  const posts = await fetchPosts(userId) // 300ms
  const notifications = await fetchNotifications(userId) // 150ms
  const stats = await fetchStats(userId) // 250ms
 
  return { user, posts, notifications, stats }
}

W takim zapisie każda kolejna operacja startuje dopiero wtedy, gdy poprzednia się zakończy i nie jest to wada async/await jako takiego tylko po prostu efekt sekwencyjnego ustawienia await. Pytanie brzmi: ile czasu zajmie wykonanie? Około 900ms plus narzut środowiska, czyli sumujesz czas wszystkich operacji, bo każda czeka na poprzednią.

Ale te zapytania są niezależne, ponieważ nie potrzebujesz wyniku fetchUser() żeby wywołać fetchPosts(), które mogłoby wystartować od razu.

Rozwiązanie: Promise.all()

Code
async function getDashboardData(userId) {
  const [user, posts, notifications, stats] = await Promise.all([
    fetchUser(userId), // 200ms
    fetchPosts(userId), // 300ms
    fetchNotifications(userId), // 150ms
    fetchStats(userId), // 250ms
  ])
 
  return { user, posts, notifications, stats }
}

Ile czasu teraz? 300ms — tyle ile najwolniejsze zapytanie.

Właśnie o to chodzi w Promise.all(), by nie przyspieszać pojedynczych requestów, ale po prostu uruchamiasz wszystkie niezależne operacje jak najwcześniej i czekasz na komplet wyników. W tym przykładzie dostajesz prawie 3x szybciej i to bez zmiany logiki biznesowej albo niepotrzebnego grzebania w backendzie.

Diagram
Różnica między sekwencyjnym await a Promise.all()

Benchmark: realna różnica

Na prostym benchmarku różnica wygląda tak:

Code
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
 
async function sequential() {
  await delay(200)
  await delay(300)
  await delay(150)
  await delay(250)
}
 
async function parallel() {
  await Promise.all([delay(200), delay(300), delay(150), delay(250)])
}
 
// Pomiar
console.time('sequential')
await sequential()
console.timeEnd('sequential') // ~900ms
 
console.time('parallel')
await parallel()
console.timeEnd('parallel') // ~300ms

W realnym świecie wynik bywa podobny albo jeszcze bardziej odczuwalny, bo operacje sieciowe mają dodatkowy narzut: połączenie, TLS, kolejki, czas serwera i opóźnienia między regionami.

Kiedy używać Promise.all()?

Używaj gdy operacje są niezależne:

Code
// ✅ Niezależne — mogą działać równolegle
const [user, products, cart] = await Promise.all([
  fetchUser(userId),
  fetchProducts(categoryId),
  fetchCart(userId),
])

Najprostsza heurystyka jest taka: jeśli potrafisz przygotować wszystkie wywołania przed pierwszym await, jest duża szansa, że możesz wykonać je równolegle.

Nie używaj gdy wynik jednej operacji jest potrzebny do drugiej:

Code
// ❌ Zależne — muszą być sekwencyjne
const user = await fetchUser(userId)
const orders = await fetchOrders(user.customerId) // potrzebuje user.customerId
const payments = await fetchPayments(orders[0].id) // potrzebuje orders

Jeśli część operacji jest zależna, a część nie, rozdziel to na etapy. Najpierw pobierz to, od czego zależą kolejne kroki, a dopiero potem odpal równoległość.

Drugi ważny warunek: niezależność to nie wszystko. Jeśli masz setki albo tysiące zadań, Promise.all() bez limitu może skończyć się rate limitingiem, problemami z pamięcią albo przeciążeniem API. Wtedy lepszy będzie batching albo pool, o których za chwilę.

Obsługa błędów: Promise.all vs Promise.allSettled

Promise.all() ma zachowanie fail-fast: pierwszy odrzucony Promise odrzuca całą agregację.

Code
try {
  const [a, b, c] = await Promise.all([
    fetchA(), // OK
    fetchB(), // FAIL — rzuca błąd
    fetchC(), // nadal się wykonuje — wynik jest tylko ignorowany
  ])
} catch (error) {
  // catch dostaje błąd z pierwszej odrzuconej Promise
  // nie dostajesz częściowych wyników w prosty sposób
  // pozostałe operacje dalej wykonują się w tle, chyba że same wspierają anulowanie
}

To bardzo dobre zachowanie wtedy, gdy komplet danych jest wymagany. Jeśli dashboard bez jednej sekcji i tak nie ma sensu, fail-fast upraszcza obsługę błędów.

Jeśli chcesz kontynuować mimo błędów, użyj Promise.allSettled():

Code
const results = await Promise.allSettled([
  fetchA(),
  fetchB(), // może się nie powieść
  fetchC(),
])
 
// results = [
//   { status: 'fulfilled', value: resultA },
//   { status: 'rejected', reason: Error },
//   { status: 'fulfilled', value: resultC },
// ]
 
// Wyciągnij tylko udane
const successful = results
  .filter((r) => r.status === 'fulfilled')
  .map((r) => r.value)
 
// Wyciągnij błędy do logowania
const errors = results
  .filter((r) => r.status === 'rejected')
  .map((r) => r.reason)

Promise.allSettled() jest świetne do sytuacji, w których częściowy sukces nadal ma wartość: widgety dashboardu, kilka źródeł danych, logowanie błędów bez blokowania całej strony.

Promise.race() i Promise.any()

Dwa inne przydatne narzędzia:

Promise.race() — pierwszy wynik (sukces lub błąd)

Code
// Generic timeout wrapper
function withTimeout(promise, ms) {
  return Promise.race([
    promise,
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Timeout')), ms),
    ),
  ])
}
 
const data = await withTimeout(fetchData(), 5000)

To dobry wzorzec dla timeoutów, ale pamiętaj: Promise.race() nie anuluje przegranego zadania. Jeśli timeout ma faktycznie przerwać fetch, użyj AbortController.

Promise.any() — pierwszy sukces (ignoruje błędy)

Code
// Fallback pattern — użyj pierwszego działającego źródła
const data = await Promise.any([
  fetchFromPrimaryAPI(),
  fetchFromBackupAPI(),
  fetchFromCache(),
])

Promise.any() rzuca AggregateError tylko jeśli wszystkie Promise zostaną odrzucone.

Wzorce dla złożonych scenariuszy

Wzorzec 1: Częściowa równoległość

Gdy masz miks zależnych i niezależnych operacji:

Code
async function getOrderDetails(orderId) {
  // Krok 1: Pobierz zamówienie
  const order = await fetchOrder(orderId)
 
  // Krok 2: Równolegle pobierz powiązane dane
  const [customer, products, shipping] = await Promise.all([
    fetchCustomer(order.customerId),
    fetchProducts(order.productIds),
    fetchShipping(order.shippingId),
  ])
 
  return { order, customer, products, shipping }
}

Wzorzec 2: Batch processing z limitem równoległości

Gdy masz dużo operacji i nie chcesz przeciążyć serwera:

Code
async function processBatch(items, fn, concurrency = 5) {
  const results = []
 
  for (let i = 0; i < items.length; i += concurrency) {
    const batch = items.slice(i, i + concurrency)
    const batchResults = await Promise.all(batch.map(fn))
    results.push(...batchResults)
  }
 
  return results
}
 
// Użycie: przetwórz 100 elementów, max 5 na raz
const users = await processBatch(userIds, fetchUser, 5)

To nadal jest sekwencja, ale na poziomie batchy. Wewnątrz każdej paczki operacje lecą równolegle, dzięki czemu ograniczasz obciążenie bez całkowitej utraty wydajności.

Wzorzec 3: Retry z exponential backoff

Code
async function fetchWithRetry(fn, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn()
    } catch (error) {
      if (i === maxRetries - 1) throw error
 
      const delay = Math.pow(2, i) * 1000 // 1s, 2s, 4s
      await new Promise((r) => setTimeout(r, delay))
    }
  }
}
 
// Użycie
const data = await fetchWithRetry(async () => {
  const response = await fetch('/api/flaky-endpoint')
 
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`)
  }
 
  return response.json()
})

Ważny detal: fetch() nie odrzuca Promise przy 404 czy 500, więc jeśli chcesz retry dla błędów HTTP, musisz rzucić wyjątek samodzielnie. Retry ma też największy sens dla operacji idempotentnych, zwłaszcza GET.

Wzorzec 4: Promise pool (zaawansowany)

Dla bardzo dużych zbiorów danych, gdzie nawet batching nie wystarczy:

Code
async function promisePool(items, fn, concurrency) {
  const results = new Array(items.length)
  let index = 0
 
  async function worker() {
    while (index < items.length) {
      const currentIndex = index++
      results[currentIndex] = await fn(items[currentIndex])
    }
  }
 
  await Promise.all(Array.from({ length: concurrency }, worker))
 
  return results
}
 
// 1000 elementów, max 10 równoległych operacji
const results = await promisePool(largeArray, processItem, 10)

W realnym projekcie często wygodniej użyć gotowego narzędzia typu p-limit albo p-map, ale dobrze rozumieć, co dzieje się pod spodem.

Typowe błędy

Błąd 1: Sekwencyjny await w pętli

Code
// ❌ Źle — każda iteracja czeka na poprzednią
const sequentialResults = []
 
for (const id of ids) {
  const data = await fetchData(id)
  sequentialResults.push(data)
}
 
// ✅ Dobrze — równoległe
const parallelResults = await Promise.all(ids.map((id) => fetchData(id)))

Błąd 2: Await w map bez Promise.all

Code
// ❌ Źle — await na tablicy NIE rozwiązuje Promise w środku
const pendingResults = await ids.map((id) => fetchData(id))
// pendingResults = [Promise, Promise, Promise...]
 
// ✅ Dobrze
const resolvedResults = await Promise.all(ids.map((id) => fetchData(id)))
// resolvedResults = [data, data, data...]

Błąd 3: forEach z async

Code
// ❌ Źle — forEach ignoruje Promise
ids.forEach(async (id) => {
  await processItem(id) // Te operacje "uciekają"
})
console.log('Done') // Wyświetli się przed zakończeniem!
 
// ✅ Dobrze
await Promise.all(ids.map((id) => processItem(id)))
console.log('Done') // Teraz czeka na wszystkie

Błąd 4: Nieograniczone Promise.all()

Code
// ❌ Ryzykowne — 2000 requestów naraz to proszenie się o problemy
const allAtOnceResults = await Promise.all(ids.map((id) => fetchData(id)))
 
// ✅ Lepiej — limit równoległości
const limitedResults = await processBatch(ids, fetchData, 10)

Promise.all() nie ma wbudowanego limitu i dla małej liczby operacji to świetne rozwiązanie, choć dla dużej liczby zadań może pogorszyć stabilność zamiast poprawić.

Debugowanie: mierzenie czasu

Code
async function withTiming(name, fn) {
  const start = performance.now()
  const result = await fn()
  const duration = performance.now() - start
  console.log(`${name}: ${duration.toFixed(2)}ms`)
  return result
}
 
// Użycie
const data = await withTiming('Dashboard data', () =>
  Promise.all([fetchUser(), fetchPosts(), fetchStats()]),
)
ScenariuszRozwiązanie
Niezależne operacjePromise.all()
Niezależne, toleruj błędyPromise.allSettled()
Pierwszy sukcesPromise.any()
Pierwszy wynik lub timeoutPromise.race()
Miks zależnych i niezależnychEtapy + Promise.all()
Wiele operacji + limitBatching, pool lub limiter

Werdykt Labu

Najważniejsza zasada nie będzie tutaj brzmiała: "zawsze używaj Promise.all()", ale raczej: uruchamiaj niezależne operacje jak najwcześniej, a czekaj na nie dopiero wtedy, gdy naprawdę potrzebujesz wyniku tych operacji.

async/await sprawia, że sekwencyjny kod pisze się bardzo naturalnie, i generalnie dlatego tak łatwo przeoczyć miejsca, w których aplikacja niepotrzebnie stoi.

Musisz sobie zadać jedno proste pytanie przy każdym await:

"Czy ta operacja naprawdę musi czekać na poprzednią?" i jeśli tak, to rób to.


Chcesz pójść dalej? Sprawdź 10 trików JavaScript dla czytelniejszego kodu.

  • Problem: sekwencyjne await1 min
  • Rozwiązanie: Promise.all()1 min
  • Benchmark: realna różnica1 min
  • Kiedy używać Promise.all()?1 min
  • Obsługa błędów: Promise.all vs Promise.allSettled1 min
  • Promise.race() i Promise.any()1 min
  • Wzorce dla złożonych scenariuszy1 min
  • Typowe błędy1 min
  • Debugowanie: mierzenie czasu1 min
  • Werdykt Labu1 min

Często zadawane pytania

Źródła i dokumentacjaZweryfikowano: 19 maja 2026

Materiały wykorzystane do weryfikacji artykułu „Async/await to pułapka — kiedy Promise.all() uratuje Twoją wydajność”:

MDN: Promise.all(), MDN: Promise.allSettled(), MDN: Promise.race(), MDN: Promise.any().

Seria

JavaScript w praktyce
Część 2 / 2
  1. 110 sztuczek w JavaScript, które sprawią, że Twój kod będzie 10x czytelniejszy
  2. Async/await to pułapka — kiedy Promise.all() uratuje Twoją wydajność
Maciej Sala

O autorze

Maciej Sala

Maciej Sala — Product Manager i Frontend Developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwija własne projekty side-projecty.

Moje artykułyWięcej o mnie

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów
REST API — zasady projektowania i dobre praktyki
REST API — zasady projektowania i dobre praktyki

Praktyczny przewodnik po projektowaniu REST API. Konwencje URL, metody HTTP, błędy, wersjonowanie, paginacja i kilka ważnych niuansów, które zwykle pomija się w prostych tutorialach.

Maciej Sala

Maciej Sala

Founder Strivelab

5 grudnia 2025
Backend dla frontendowca: serwer, bazy danych i API
Backend dla frontendowca: serwer, bazy danych i API

Pierwsza część serii Backend dla frontendowca: architektura aplikacji, serwer, bazy danych, API, status code, paginacja, idempotency, BFF i CORS.

Maciej Sala

Maciej Sala

Founder Strivelab

28 lipca 2025
10 sztuczek w JavaScript, które sprawią, że Twój kod będzie 10x czytelniejszy
10 sztuczek w JavaScript, które sprawią, że Twój kod będzie 10x czytelniejszy

Poznaj 10 technik JavaScript, które realnie poprawiają czytelność kodu. Optional chaining, nullish coalescing, destrukturyzacja, metody tablic i więcej.

Maciej Sala

Maciej Sala

Founder Strivelab

12 czerwca 2025
Poprzedni wpisE2E testy w Next.js App Router – kompletny setup Cypress + CI/CDCypress E2E w Next.js App Router krok po kroku. Konfiguracja, fixtures, custom commands, CI i wzorce, które ograniczają flaky testy.
Maciej Sala

Maciej Sala

Founder Strivelab

31 października 2025
Następny wpisCore Web Vitals — jak przyspieszyć stronę i poprawić pozycję w GoogleCore Web Vitals to kluczowe metryki wydajności i doświadczenia użytkownika. Poznaj LCP, INP i CLS oraz zobacz, jak je mierzyć, monitorować i poprawiać w praktyce.
Maciej Sala

Maciej Sala

Founder Strivelab

14 listopada 2025