Memahami JavaScript Promises dan Async/Await - Panduan Lengkap


Salah satu konsep paling penting dalam JavaScript modern adalah asynchronous programming. Jika Anda pernah bingung dengan callbacks, promises, atau async/await - artikel ini untuk Anda.

Mengapa Async Programming Penting?

JavaScript adalah single-threaded. Artinya, hanya satu operasi yang bisa dijalankan pada satu waktu. Tapi bagaimana jika Anda perlu:

  • Fetch data dari API
  • Baca file dari disk
  • Query database
  • Tunggu user input

Operasi ini memakan waktu. Jika JavaScript menunggu selesai sebelum lanjut ke kode berikutnya, aplikasi Anda akan freeze.

Solusinya: asynchronous code. JavaScript akan mulai operasi, lanjut ke kode berikutnya, dan handle hasil operasi ketika sudah selesai.

Era Callback Hell

Dulu, JavaScript menggunakan callbacks untuk handle async operations:

getUser(userId, function(user) {
  getPosts(user.id, function(posts) {
    getComments(posts[0].id, function(comments) {
      console.log(comments);
      // Dan seterusnya...
    });
  });
});

Ini disebut “callback hell” atau “pyramid of doom”. Kode jadi susah dibaca dan di-maintain.

Enter Promises

Promises adalah cara yang lebih baik untuk handle async operations. Promise adalah object yang merepresentasikan eventual completion (atau failure) dari async operation.

Promise punya tiga states:

  • Pending - operasi masih berjalan
  • Fulfilled - operasi selesai dengan sukses
  • Rejected - operasi gagal

Contoh dasar:

const promise = new Promise((resolve, reject) => {
  // Simulasi async operation
  setTimeout(() => {
    const success = true;

    if (success) {
      resolve('Operation successful!');
    } else {
      reject('Operation failed!');
    }
  }, 2000);
});

promise
  .then(result => console.log(result))
  .catch(error => console.error(error));

Promise Chaining

Anda bisa chain promises untuk handle sequential async operations:

getUser(userId)
  .then(user => getPosts(user.id))
  .then(posts => getComments(posts[0].id))
  .then(comments => console.log(comments))
  .catch(error => console.error('Error:', error));

Jauh lebih readable daripada callbacks.

Async/Await - Syntactic Sugar

ES2017 memperkenalkan async/await, yang membuat async code terlihat seperti synchronous code:

async function fetchUserData(userId) {
  try {
    const user = await getUser(userId);
    const posts = await getPosts(user.id);
    const comments = await getComments(posts[0].id);
    console.log(comments);
  } catch (error) {
    console.error('Error:', error);
  }
}

Ini adalah kode yang sama dengan promise chaining di atas, tapi lebih mudah dibaca.

Bagaimana Async/Await Bekerja

Keyword async sebelum function membuat function tersebut selalu return Promise.

Keyword await hanya bisa digunakan di dalam async function. Ini membuat JavaScript “menunggu” promise selesai sebelum lanjut.

Tapi ingat - await tidak block seluruh aplikasi. Hanya code dalam function tersebut yang “menunggu”.

Contoh Praktis - Fetch API

Fetch API adalah cara modern untuk request data dari server:

async function getData() {
  try {
    const response = await fetch('https://api.example.com/data');

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    console.log(data);
    return data;
  } catch (error) {
    console.error('Fetch error:', error);
  }
}

Perhatikan bahwa fetch() return Promise, dan response.json() juga return Promise. Kita await keduanya.

Error Handling

Dengan promises, Anda menggunakan .catch():

fetchData()
  .then(data => processData(data))
  .catch(error => console.error(error));

Dengan async/await, Anda menggunakan try/catch:

async function processData() {
  try {
    const data = await fetchData();
    const processed = await processData(data);
    return processed;
  } catch (error) {
    console.error('Error:', error);
    // Handle error appropriately
  }
}

Promise.all - Parallel Operations

Jika Anda punya multiple async operations yang tidak depend satu sama lain, jalankan secara parallel:

async function fetchMultipleData() {
  try {
    const [users, posts, comments] = await Promise.all([
      fetch('/api/users').then(r => r.json()),
      fetch('/api/posts').then(r => r.json()),
      fetch('/api/comments').then(r => r.json())
    ]);

    console.log({ users, posts, comments });
  } catch (error) {
    console.error('Error fetching data:', error);
  }
}

Promise.all menunggu semua promises selesai, atau reject jika salah satu gagal.

Promise.allSettled - Untuk Robust Error Handling

Jika Anda ingin semua promises selesai terlepas dari success atau failure:

async function fetchAllData() {
  const results = await Promise.allSettled([
    fetch('/api/users').then(r => r.json()),
    fetch('/api/posts').then(r => r.json()),
    fetch('/api/comments').then(r => r.json())
  ]);

  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      console.log(`Result ${index}:`, result.value);
    } else {
      console.error(`Error ${index}:`, result.reason);
    }
  });
}

Promise.race - First to Finish Wins

Kadang Anda hanya perlu yang tercepat:

async function fetchWithTimeout(url, timeout = 5000) {
  const timeoutPromise = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Request timeout')), timeout)
  );

  const fetchPromise = fetch(url).then(r => r.json());

  return Promise.race([fetchPromise, timeoutPromise]);
}

Ini berguna untuk implement timeout pada requests.

Common Mistakes

1. Lupa await:

// SALAH
async function getData() {
  const data = fetchData(); // Lupa await
  console.log(data); // Ini akan log Promise object, bukan data
}

// BENAR
async function getData() {
  const data = await fetchData();
  console.log(data);
}

2. Await di loop tanpa perlu:

// INEFFICIENT - sequential
async function processItems(items) {
  for (const item of items) {
    await processItem(item); // Satu per satu
  }
}

// BETTER - parallel
async function processItems(items) {
  await Promise.all(items.map(item => processItem(item)));
}

3. Tidak handle errors:

// SALAH
async function getData() {
  const data = await fetchData();
  return data;
} // Error tidak ter-handle

// BENAR
async function getData() {
  try {
    const data = await fetchData();
    return data;
  } catch (error) {
    console.error('Error:', error);
    throw error; // atau handle sesuai kebutuhan
  }
}

Real-World Example - User Authentication

async function loginUser(email, password) {
  try {
    // Validate input
    if (!email || !password) {
      throw new Error('Email and password required');
    }

    // Login request
    const response = await fetch('/api/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password })
    });

    if (!response.ok) {
      throw new Error('Login failed');
    }

    const { token, user } = await response.json();

    // Store token
    localStorage.setItem('authToken', token);

    // Fetch additional user data
    const userData = await fetchUserProfile(user.id);

    return { user: userData, token };
  } catch (error) {
    console.error('Login error:', error);
    throw error;
  }
}

Tips dan Best Practices

  1. Selalu handle errors - gunakan try/catch atau .catch()
  2. Jangan overuse await - gunakan Promise.all untuk parallel operations
  3. Return promises dari functions - lebih fleksibel
  4. Consistent error handling - decide apakah throw atau return error
  5. Use TypeScript - untuk better type safety dengan async code

Debugging Async Code

Debugging async code bisa tricky. Tips:

  • Gunakan console.log untuk track execution flow
  • Chrome DevTools support async stack traces
  • Gunakan debugger statement
  • VS Code punya excellent async debugging support

Kesimpulan

Async programming adalah fundamental dalam JavaScript modern. Promises dan async/await membuat async code lebih readable dan maintainable.

Kuncinya:

  • Promises untuk handle single async operations
  • Promise.all/allSettled/race untuk multiple operations
  • Async/await untuk cleaner syntax
  • Always handle errors
  • Understand parallel vs sequential execution

Praktek dengan examples di atas. Buat aplikasi kecil yang fetch data dari public APIs untuk latihan.

Setelah comfortable dengan concepts ini, async JavaScript akan jadi second nature.