Parallel va Sequential data fetching — farqi, qachon qaysinisi

~15 daq · Next.js · Ma'lumot Olish

Muammodan boshlaylik

Siz foydalanuvchi profil sahifasini yozdingiz. Sahifada ikkita ma'lumot kerak: foydalanuvchi ma'lumotlari va uning postlari.

export default async function ProfilePage({ params }) {
  const { id } = await params

  const user  = await fetch(`/api/users/${id}`).then(r => r.json())   // 300ms
  const posts = await fetch(`/api/posts?userId=${id}`).then(r => r.json()) // 500ms
}

Bu kod ishlaydi. Lekin qancha vaqt oladi? 800ms — 300 + 500. Chunki ikkinchi fetch birinchisi tugaguncha boshlanmaydi.

Holbuki bu ikki so'rov bir-biriga bog'liq emas. Ikkalasini bir vaqtda boshlasa bo'ladi — 500ms yetardi.


Sequential — ketma-ket

Yuqoridagi kod — sequential (ketma-ket). Har bir await keyingisini to'xtatib turadi.

Sequential:
  fetch(user)  ──────────── 300ms ──────────── ✓
                                               fetch(posts) ──────────────── 500ms ──── ✓
  Jami: 800ms

Qachon kerak: ikkinchi so'rov birinchisining natijasiga bog'liq bo'lganda.

// Bu holda sequential to'g'ri — post ID user dan keladi
const user = await fetch(`/api/users/${id}`).then(r => r.json())
const post = await fetch(`/api/posts/${user.latestPostId}`).then(r => r.json())
//                                         ↑ user dan olingan

user.latestPostId — faqat user kelgandan keyin ma'lum. Bu yerda sequential majburiy.


Parallel — bir vaqtda

Bir-biriga bog'liq bo'lmagan so'rovlarni Promise.all bilan parallel boshlash:

export default async function ProfilePage({ params }) {
  const { id } = await params

  // Ikkalasi bir vaqtda boshlanadi
  const [user, posts] = await Promise.all([
    fetch(`/api/users/${id}`).then(r => r.json()),   // 300ms ┐
    fetch(`/api/posts?userId=${id}`).then(r => r.json()), // 500ms ┘ parallel
  ])

  return (
    <div>
      <h1>{user.name}</h1>
      <PostList posts={posts} />
    </div>
  )
}
Parallel:
  fetch(user)  ──────────── 300ms ──── ✓
  fetch(posts) ──────────────────────────── 500ms ──── ✓
  Jami: 500ms  (eng uzun so'rov)

300ms tejaldi.


Promise.all ning kamchiligi

Promise.all da bitta so'rov xato bersa — hammasi bekor qilinadi:

// ❌ — posts xato bersa, user ham yo'qoladi
const [user, posts] = await Promise.all([
  fetch('/api/user').then(r => r.json()),
  fetch('/api/posts').then(r => r.json()),  // xato bersa...
])
// user ham olinmaydi

Xatolarni alohida boshqarish uchun Promise.allSettled:

const [userResult, postsResult] = await Promise.allSettled([
  fetch('/api/user').then(r => r.json()),
  fetch('/api/posts').then(r => r.json()),
])

const user  = userResult.status  === 'fulfilled' ? userResult.value  : null
const posts = postsResult.status === 'fulfilled' ? postsResult.value : []

Har biri mustaqil — biri xato bersa ikkinchisi ishlaydi.


Suspense bilan parallel — eng optimal

Promise.all da hammasi tayyor bo'lguncha kutiladi. Streaming bilan birlashtirish yanada yaxshi — har komponent tayyor bo'lganda ko'rinadi:

// app/profile/[id]/page.tsx
import { Suspense } from 'react'
import { UserInfo } from './UserInfo'
import { UserPosts } from './UserPosts'

export default function ProfilePage({ params }) {
  return (
    <div>
      <Suspense fallback={<p>Foydalanuvchi yuklanmoqda...</p>}>
        <UserInfo id={params.id} />      {/* 300ms da ko'rinadi */}
      </Suspense>

      <Suspense fallback={<p>Postlar yuklanmoqda...</p>}>
        <UserPosts userId={params.id} /> {/* 500ms da ko'rinadi */}
      </Suspense>
    </div>
  )
}
// UserInfo.tsx
export async function UserInfo({ id }) {
  const user = await fetch(`/api/users/${id}`).then(r => r.json()) // 300ms
  return <h1>{user.name}</h1>
}

// UserPosts.tsx
export async function UserPosts({ userId }) {
  const posts = await fetch(`/api/posts?userId=${userId}`).then(r => r.json()) // 500ms
  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}
Suspense + parallel:
  UserInfo  ──────────── 300ms ──── ko'rinadi ✓
  UserPosts ──────────────────────────── 500ms ──── ko'rinadi ✓
  Foydalanuvchi 300ms da birinchi kontent ko'radi

Promise.all bilan farqi: Promise.all da 500ms kutiladi, keyin ikkalasi birga ko'rinadi. Suspense bilan 300ms da UserInfo ko'rinadi, 500ms da UserPosts qo'shiladi.


Qachon qaysinisini tanlash

Sequential  → ikkinchi so'rov birinchisining natijasiga bog'liq
              user → user.latestPostId → post

Promise.all → bog'liq emas, lekin ikkalasi tayyor bo'lguncha kutish kerak
              dashboard statistikasi — hammasi bir vaqtda ko'rinsin

Suspense    → bog'liq emas, har biri tayyor bo'lganda ko'rinsin
              profil sahifasi — user tez, postlar sekin

Tez-tez uchraydigan xatolar

1. Bog'liq bo'lmagan so'rovlarni sequential yozish

// ❌ — 800ms
const user    = await getUser(id)
const posts   = await getPosts(id)
const friends = await getFriends(id)

// ✅ — 300ms (eng uzun so'rov)
const [user, posts, friends] = await Promise.all([
  getUser(id),
  getPosts(id),
  getFriends(id),
])

2. Promise.all da xato boshqaruvini unutish

// ❌ — bitta xato hammani to'xtatadi
const [a, b] = await Promise.all([fetchA(), fetchB()])

// ✅ — xatolarni alohida boshqarish
const [aResult, bResult] = await Promise.allSettled([fetchA(), fetchB()])

3. Suspense ichida Promise.all ishlatish — streaming yo'qoladi

// ❌ — Suspense bor, lekin streaming yo'q
// page.tsx
export default async function Page() {
  const [user, posts] = await Promise.all([getUser(), getPosts()])
  // hammasi tayyor bo'lguncha sahifa ko'rinmaydi
  return (
    <Suspense>  {/* bu yerda Suspense keraksiz */}
      <Profile user={user} posts={posts} />
    </Suspense>
  )
}

// ✅ — har komponent o'zi fetch qiladi
export default function Page() {
  return (
    <>
      <Suspense fallback={<Skeleton />}><UserInfo /></Suspense>
      <Suspense fallback={<Skeleton />}><UserPosts /></Suspense>
    </>
  )
}

Xulosa

Sequential   → await A → await B → jami: A + B vaqt
               qachon: B ning natijasi A ga bog'liq

Promise.all  → await [A, B] → jami: max(A, B) vaqt
               qachon: bog'liq emas, ikkalasi birga ko'rinsin

Suspense     → A va B parallel, tayyor bo'lgani ko'rinadi
               qachon: bog'liq emas, eng yaxshi UX kerak

Keyingi dars

19-dars: Loading UI va Suspense — skeleton, fallback

Yuklanish holatini to'g'ri ko'rsatish — foydalanuvchi tajribasining muhim qismi. Skeleton UI qanday yoziladi, qayerda ishlatiladi.