Parallel vs Sequential Fetching

Request Waterfalls

Each await waits before the next line starts. If requests do not depend on each other, awaiting them one by one creates a waterfall.

// Sequential: 300 + 200 + 100 = ~600 ms
const product = await getProduct(id);
const reviews = await getReviews(id);
const related = await getRelated(id);

// Parallel: max(300, 200, 100) = ~300 ms
const [product2, reviews2, related2] = await Promise.all([getProduct(id), getReviews(id), getRelated(id)]);

See the Difference

Example

Fake API calls with fixed delays. The total time is rounded to 100 ms:

function fakeApi<T>(name: string, ms: number, value: T): Promise<T> {
  return new Promise((resolve) => setTimeout(() => { console.log('  done: ' + name); resolve(value); }, ms));
}

const product = () => fakeApi('product', 300, { id: 42, name: 'Redmi Note 14' });
const reviews = () => fakeApi('reviews', 200, ['Great phone', 'Good battery']);
const related = () => fakeApi('related', 100, [43, 44]);

async function main() {
  let start = performance.now();
  console.log('sequential');
  await product(); await reviews(); await related();
  console.log('  ~' + Math.round((performance.now() - start) / 100) * 100 + ' ms');

  start = performance.now();
  console.log('parallel');
  const [p, r, rel] = await Promise.all([product(), reviews(), related()]);
  console.log('  ~' + Math.round((performance.now() - start) / 100) * 100 + ' ms');
  console.log(p.name, r.length, 'reviews', rel.length, 'related');
}
main();

Output

sequential
  done: product
  done: reviews
  done: related
  ~600 ms
parallel
  done: related
  done: reviews
  done: product
  ~300 ms
Redmi Note 14 2 reviews 2 related

When Sequential Is Right

If the second call needs the first one's result (product → its seller), it must wait. You can still show the first part early with <Suspense> so the user is not blocked:

export default async function Page({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params;
  const product = await getProduct(Number(id));
  return (
    <>
      <h1>{product.name}</h1>
      <Suspense fallback={<p>Loading seller...</p>}>
        <SellerInfo sellerId={product.sellerId} /> {/* async component fetches on its own */}
      </Suspense>
    </>
  );
}

Promise.allSettled for Optional Parts

Tip: If recommendations fail, the product page should still render. Use Promise.allSettled and treat failed optional parts as empty.