Parallel va Sequential data fetching — farqi, qachon qaysinisi
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.