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
- Selalu handle errors - gunakan try/catch atau .catch()
- Jangan overuse await - gunakan Promise.all untuk parallel operations
- Return promises dari functions - lebih fleksibel
- Consistent error handling - decide apakah throw atau return error
- Use TypeScript - untuk better type safety dengan async code
Debugging Async Code
Debugging async code bisa tricky. Tips:
- Gunakan
console.loguntuk track execution flow - Chrome DevTools support async stack traces
- Gunakan
debuggerstatement - 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.