WEB-PERFORMANCE FRONTEND DATA-FETCHING OPTIMIZATION JAVASCRIPT REACT BEST-PRACTICES USER-EXPERIENCE SCALABILITY

Mengatasi Masalah Waterfall pada Data Fetching di Aplikasi Web Modern

⏱️ 13 menit baca
👨‍💻

Mengatasi Masalah Waterfall pada Data Fetching di Aplikasi Web Modern

Sebagai developer web, kita semua mendambakan aplikasi yang cepat, responsif, dan memberikan pengalaman pengguna yang mulus. Namun, ada satu masalah umum yang seringkali menjadi biang keladi lambatnya loading data di aplikasi kita: masalah waterfall pada data fetching. Pernahkah Anda merasa aplikasi Anda memuat data secara berurutan, satu per satu, sehingga terasa lambat dan menyebalkan? Nah, itu dia waterfall!

Artikel ini akan membahas tuntas apa itu masalah waterfall, mengapa ia terjadi, dan yang terpenting, bagaimana strategi praktis untuk mengatasinya. Dengan memahami dan menerapkan pola-pola ini, Anda bisa membuat aplikasi web Anda terasa jauh lebih cepat dan menyenangkan bagi pengguna. Mari kita selami!

1. Pendahuluan

Di era aplikasi web modern, terutama Single Page Applications (SPAs) yang sangat bergantung pada data dari API, efisiensi dalam mengambil data adalah kunci. Bayangkan Anda sedang membuka aplikasi e-commerce. Anda ingin melihat daftar produk, lalu detail produk, lalu ulasan, dan mungkin rekomendasi produk terkait. Jika setiap permintaan data ini harus menunggu yang sebelumnya selesai, pengalaman Anda akan seperti menunggu tetesan air satu per satu dari keran yang bocor – lambat dan tidak efisien. Inilah esensi dari masalah waterfall.

Masalah waterfall bukan hanya sekadar “lambat”, tapi juga bisa menyebabkan:

Sebagai developer, kita memiliki kontrol atas bagaimana data diambil. Dengan strategi yang tepat, kita bisa mengubah tetesan air menjadi aliran sungai yang deras, membuat aplikasi kita lebih cepat dan responsif.

2. Apa Itu Masalah Waterfall dalam Data Fetching?

📌 Analogi Sederhana: Bayangkan Anda ingin membuat secangkir kopi susu, tapi Anda harus melakukan setiap langkah secara berurutan:

  1. Rebus air (tunggu sampai mendidih).
  2. Seduh kopi (tunggu sampai selesai).
  3. Panaskan susu (tunggu sampai selesai).
  4. Campurkan semuanya.

Jika Anda tidak bisa melakukan langkah 2 dan 3 secara bersamaan, atau bahkan memulai langkah 2 dan 3 sebelum langkah 1 selesai, maka Anda mengalami “waterfall”. Setiap langkah harus menunggu langkah sebelumnya selesai sepenuhnya.

Dalam konteks data fetching, masalah waterfall terjadi ketika beberapa permintaan data (request) yang saling bergantung atau bahkan tidak bergantung, dieksekusi secara berurutan (sequential) alih-alih secara paralel (bersamaan). Ini berarti request B baru akan dimulai setelah request A selesai, request C baru dimulai setelah request B selesai, dan seterusnya. Akibatnya, total waktu yang dibutuhkan untuk mengambil semua data adalah jumlah dari durasi masing-masing request, ditambah latensi jaringan di antara setiap request.

3. Mengapa Waterfall Terjadi?

Masalah waterfall seringkali muncul karena beberapa alasan:

a. Ketergantungan Data (Data Dependencies)

Ini adalah penyebab paling umum. Anda memerlukan ID pengguna dari API pertama untuk bisa mengambil daftar pesanan pengguna dari API kedua. Atau Anda perlu daftar kategori produk dari API A, lalu menggunakan ID kategori tersebut untuk mengambil produk-produk di API B.

// ❌ Contoh Waterfall karena Ketergantungan Data
async function getUserAndOrders(userId) {
  // Langkah 1: Ambil detail user
  const userResponse = await fetch(`/api/users/${userId}`);
  const userData = await userResponse.json();

  // Langkah 2: Gunakan ID user untuk ambil pesanan
  // Ini baru akan dieksekusi setelah langkah 1 selesai
  const ordersResponse = await fetch(`/api/users/${userData.id}/orders`);
  const ordersData = await ordersResponse.json();

  return { userData, ordersData };
}

Dalam contoh di atas, permintaan orders tidak bisa dimulai sebelum userData.id tersedia, yang berarti permintaan users harus selesai terlebih dahulu.

b. Implementasi yang Tidak Optimal

Terkadang, waterfall terjadi bukan karena ketergantungan data yang mutlak, melainkan karena cara kode ditulis yang secara tidak sengaja membuat permintaan dieksekusi secara berurutan. Ini sering terjadi ketika developer menggunakan await secara berlebihan untuk permintaan yang sebenarnya bisa dijalankan secara paralel.

// ❌ Contoh Waterfall karena Implementasi (padahal bisa paralel)
async function getProductsAndCategories() {
  // Langkah 1: Ambil produk
  const productsResponse = await fetch('/api/products');
  const productsData = await productsResponse.json();

  // Langkah 2: Ambil kategori
  // Ini baru akan dieksekusi setelah langkah 1 selesai,
  // padahal tidak ada ketergantungan langsung.
  const categoriesResponse = await fetch('/api/categories');
  const categoriesData = await categoriesResponse.json();

  return { productsData, categoriesData };
}

Di sini, permintaan produk dan kategori sebenarnya tidak saling bergantung, tapi penggunaan await secara sekuensial membuatnya berjalan satu per satu.

c. Struktur Komponen UI

Dalam framework seperti React, masalah waterfall juga bisa muncul ketika komponen induk memuat data, lalu komponen anak memuat data lagi berdasarkan data dari induk, dan seterusnya, menciptakan rantai permintaan yang dalam.

// ❌ Contoh Waterfall karena Struktur Komponen React
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    async function fetchUser() {
      const response = await fetch(`/api/users/${userId}`);
      const data = await response.json();
      setUser(data);
    }
    fetchUser();
  }, [userId]);

  if (!user) return <LoadingSpinner />;

  return (
    <div>
      <h1>{user.name}</h1>
      {/* Komponen anak ini baru bisa render dan fetch data setelah `user` ada */}
      <UserOrders userId={user.id} />
    </div>
  );
}

function UserOrders({ userId }) {
  const [orders, setOrders] = useState([]);

  useEffect(() => {
    async function fetchOrders() {
      const response = await fetch(`/api/users/${userId}/orders`);
      const data = await response.json();
      setOrders(data);
    }
    fetchOrders();
  }, [userId]);

  if (orders.length === 0) return <p>No orders.</p>;

  return (
    <ul>
      {orders.map(order => <li key={order.id}>{order.name}</li>)}
    </ul>
  );
}

UserOrders hanya akan mulai mengambil data setelah UserProfile selesai mengambil data user dan merendernya, menciptakan waterfall yang jelas.

4. Dampak Buruk Waterfall pada User Experience

Masalah waterfall tidak hanya sekadar isu teknis; ia berdampak langsung pada bagaimana pengguna merasakan aplikasi Anda.

5. Strategi Mengatasi Masalah Waterfall

Kabar baiknya, ada beberapa strategi ampuh yang bisa kita terapkan untuk mengatasi masalah waterfall.

a. ✅ Parallel Fetching

Ini adalah strategi paling dasar dan efektif untuk permintaan yang tidak memiliki ketergantungan satu sama lain. Daripada menunggu satu request selesai untuk memulai yang lain, kita bisa menjalankannya secara bersamaan menggunakan Promise.all().

// ✅ Parallel Fetching dengan Promise.all()
async function getProductsAndCategoriesParallel() {
  const [productsResponse, categoriesResponse] = await Promise.all([
    fetch('/api/products'),
    fetch('/api/categories')
  ]);

  const productsData = await productsResponse.json();
  const categoriesData = await categoriesResponse.json();

  return { productsData, categoriesData };
}

Dengan Promise.all(), kedua permintaan akan dimulai hampir bersamaan. Total waktu yang dibutuhkan akan ditentukan oleh permintaan terlama, bukan jumlah dari keduanya.

Untuk skenario dengan ketergantungan, Anda bisa memecah menjadi langkah-langkah paralel setelah mendapatkan data kunci:

// ✅ Parallel Fetching dengan Ketergantungan Data
async function getUserAndRelatedData(userId) {
  // Langkah 1: Ambil detail user (sequential pertama)
  const userResponse = await fetch(`/api/users/${userId}`);
  const userData = await userResponse.json();

  // Langkah 2: Setelah user data didapat, jalankan permintaan lain secara paralel
  // Contoh: ambil pesanan DAN ambil rekomendasi produk
  const [ordersResponse, recommendationsResponse] = await Promise.all([
    fetch(`/api/users/${userData.id}/orders`),
    fetch(`/api/recommendations?userId=${userData.id}`)
  ]);

  const ordersData = await ordersResponse.json();
  const recommendationsData = await recommendationsResponse.json();

  return { userData, ordersData, recommendationsData };
}

b. 💡 Pre-fetching dan Pre-loading

Strategi ini melibatkan pengambilan data yang kemungkinan besar akan dibutuhkan pengguna, bahkan sebelum pengguna secara eksplisit memintanya.

Pre-fetching bisa diimplementasikan dengan fetch() biasa atau menggunakan library seperti React Query/SWR yang memiliki fitur pre-fetching built-in. Untuk pre-loading, bisa juga dengan link rel="preload" untuk sumber daya statis, atau memicu request data API di awal.

// Contoh Pre-fetching saat hover link
const productLink = document.getElementById('product-link-123');
productLink.addEventListener('mouseover', () => {
  // Mulai fetch detail produk saat user hover
  fetch('/api/products/123').then(response => response.json())
    .then(data => console.log('Product 123 prefetched:', data))
    .catch(error => console.error('Prefetch error:', error));
});

c. 📦 Batching Requests

Batching adalah teknik di mana Anda menggabungkan beberapa permintaan API terpisah menjadi satu permintaan tunggal ke server. Server kemudian memproses semua permintaan tersebut dan mengembalikan satu respons yang berisi hasil dari semua permintaan. Ini sangat berguna jika Anda memiliki banyak permintaan kecil yang harus dilakukan ke endpoint yang sama atau terkait.

// Contoh Batching Request (membutuhkan dukungan backend)
async function getMultipleProducts(productIds) {
  // Kirim array ID produk dalam satu request
  const response = await fetch('/api/products/batch', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ids: productIds })
  });
  const data = await response.json(); // Akan mengembalikan array produk
  return data;
}

// Daripada:
// await fetch('/api/products/1');
// await fetch('/api/products/2');
// await fetch('/api/products/3');
// ...

d. 🧩 Co-locating Data Requirements (dengan GraphQL atau Komponen)

Ide di balik co-locating data requirements adalah menempatkan logika fetching data sedekat mungkin dengan komponen UI yang membutuhkannya.

e. 🚀 Memanfaatkan Server-Side Rendering (SSR) / Static Site Generation (SSG)

Untuk aplikasi yang sebagian besar kontennya statis atau bisa diproses di server, SSR dan SSG adalah cara terbaik untuk menghindari waterfall di sisi klien sama sekali.

Baik SSR maupun SSG memindahkan beban fetching data dari browser ke server, yang seringkali memiliki koneksi lebih cepat ke database/API internal, sehingga mengurangi latensi yang dirasakan pengguna. Framework seperti Next.js dan Astro sangat unggul dalam hal ini.

f. 🛠️ Menggunakan Tools dan Libraries

Banyak library modern yang dirancang untuk mengatasi masalah data fetching, termasuk waterfall:

6. Tips Praktis dan Best Practices

Kesimpulan

Masalah waterfall pada data fetching adalah tantangan umum di aplikasi web modern yang dapat secara signifikan menghambat performa dan pengalaman pengguna. Dengan memahami penyebabnya—mulai dari ketergantungan data hingga implementasi kode yang tidak optimal—kita sebagai developer dapat mengambil langkah proaktif.

Strategi seperti parallel fetching dengan Promise.all(), pre-fetching data yang mungkin dibutuhkan, batching requests untuk mengurangi overhead, co-locating data requirements dengan GraphQL, hingga memanfaatkan SSR/SSG untuk memindahkan beban ke server, adalah senjata ampuh yang bisa Anda gunakan.

💡 Actionable Takeaway: Selalu mulai dengan memvisualisasikan request Anda di Chrome DevTools. Setelah itu, identifikasi ketergantungan dan terapkan strategi parallel fetching terlebih dahulu. Untuk skenario yang lebih kompleks, pertimbangkan batching, pre-fetching, atau bahkan arsitektur data fetching yang lebih canggih seperti GraphQL atau SSR. Dengan sedikit usaha, Anda bisa mengubah aplikasi yang lambat menjadi pengalaman yang cepat dan menyenangkan bagi setiap pengguna.

🔗 Baca Juga