[Medium] π Promise
Apa itu Promise?β
Promise adalah fitur ES6 yang diperkenalkan terutama untuk menyelesaikan callback hell dan membuat kode asinkron lebih mudah dibaca dan dipelihara. Promise merepresentasikan penyelesaian akhir (atau kegagalan) dari operasi asinkron dan nilai yang dihasilkannya.
Promise memiliki tiga state:
- pending: state awal
- fulfilled: operasi berhasil diselesaikan
- rejected: operasi gagal
Penggunaan Dasar (Basic Usage)β
Membuat Promiseβ
const myPromise = new Promise((resolve, reject) => {
// operasi asinkron
const success = true;
if (success) {
resolve('Berhasil!'); // Promise menjadi fulfilled
} else {
reject('Gagal!'); // Promise menjadi rejected
}
});
myPromise
.then((result) => {
console.log(result); // 'Berhasil!'
})
.catch((error) => {
console.log(error); // 'Gagal!'
});
Contoh dunia nyata: menangani permintaan APIβ
// fungsi bersama untuk permintaan API
function fetchData(url) {
return fetch(url)
.then((response) => {
// periksa apakah respons dalam rentang 200~299
if (!response.ok) {
throw new Error('Respons jaringan tidak ok ' + response.statusText);
}
return response.json(); // ubah respons ke JSON dan kembalikan
})
.catch((error) => {
// tangani masalah jaringan atau kegagalan permintaan
console.log('Ada masalah dengan operasi fetch Anda:', error);
throw error; // lempar ulang untuk penanganan upstream
});
}
fetchData('https://jsonplaceholder.typicode.com/users/1')
.then((userData) => {
console.log('Data pengguna diterima:', userData);
})
.catch((error) => {
console.log('Error:', error.message);
});
Metode Promise (Promise Methods)β
.then() / .catch() / .finally()β
promise
.then((result) => {
// tangani keberhasilan
return result;
})
.catch((error) => {
// tangani error
console.error(error);
})
.finally(() => {
// berjalan terlepas dari berhasil atau gagal
console.log('Promise selesai');
});
Promise.all()β
Resolve saat semua Promise resolve, dan reject langsung saat salah satu Promise reject.
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve) =>
setTimeout(() => resolve('foo'), 100)
);
const promise3 = Promise.resolve(42);
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values); // [3, 'foo', 42]
});
Kapan digunakan: lanjutkan hanya setelah semua pemanggilan API berhasil.
Promise.race()β
Mengembalikan hasil dari Promise pertama yang selesai (fulfilled atau rejected).
const promise1 = new Promise((resolve) =>
setTimeout(() => resolve('pertama'), 500)
);
const promise2 = new Promise((resolve) =>
setTimeout(() => resolve('kedua'), 100)
);
Promise.race([promise1, promise2]).then((value) => {
console.log(value); // 'kedua' (lebih cepat)
});
Kapan digunakan: penanganan timeout permintaan, atau mengambil respons tercepat.
Promise.allSettled()β
Menunggu semua Promise selesai (fulfilled/rejected), lalu mengembalikan semua hasil.
const promise1 = Promise.resolve(3);
const promise2 = Promise.reject('Error');
const promise3 = Promise.resolve(42);
Promise.allSettled([promise1, promise2, promise3]).then((results) => {
console.log(results);
// [
// { status: 'fulfilled', value: 3 },
// { status: 'rejected', reason: 'Error' },
// { status: 'fulfilled', value: 42 }
// ]
});
Kapan digunakan: Anda memerlukan semua hasil, bahkan jika beberapa gagal.
Promise.any()β
Resolve dengan Promise pertama yang fulfilled. Reject hanya jika semua Promise reject.
const promise1 = Promise.reject('Error 1');
const promise2 = new Promise((resolve) =>
setTimeout(() => resolve('Berhasil'), 100)
);
const promise3 = Promise.reject('Error 2');
Promise.any([promise1, promise2, promise3]).then((value) => {
console.log(value); // 'Berhasil'
});
Kapan digunakan: sumber daya fallback di mana satu keberhasilan sudah cukup.
Pertanyaan Wawancara (Interview Questions)β
Pertanyaan 1: Promise chaining dan penanganan errorβ
Prediksi outputnya:
Promise.resolve(1)
.then((x) => x + 1)
.then(() => {
throw new Error('My Error');
})
.catch((e) => 1)
.then((x) => x + 1)
.then((x) => console.log(x))
.catch((e) => console.log('Ini tidak akan berjalan'));
Penjelasan langkah demi langkahβ
Promise.resolve(1) // mengembalikan 1
.then((x) => x + 1) // x = 1, mengembalikan 2
.then(() => {
throw new Error('My Error'); // throw -> ke catch
})
.catch((e) => 1) // menangkap dan mengembalikan nilai normal 1
.then((x) => x + 1) // x = 1, mengembalikan 2
.then((x) => console.log(x)) // mencetak 2
.catch((e) => console.log('Ini tidak akan berjalan')); // tidak dieksekusi
Jawaban: 2
Konsep Kunciβ
catchbisa memulihkan dengan nilai normal: saatcatch()mengembalikan nilai normal, chain berlanjut dalam mode fulfilled.thensetelahcatchtetap berjalan: karena error telah ditangani.catchterakhir tidak berjalan: tidak ada error baru yang dilempar.
Jika Anda ingin error terus merambat, lempar ulang di catch:
Promise.resolve(1)
.then((x) => x + 1)
.then(() => {
throw new Error('My Error');
})
.catch((e) => {
console.log('Error tertangkap');
throw e; // lempar ulang
})
.then((x) => x + 1) // tidak akan berjalan
.then((x) => console.log(x)) // tidak akan berjalan
.catch((e) => console.log('Ini akan berjalan')); // akan berjalan
Pertanyaan 2: Event Loop dan urutan eksekusiβ
Pertanyaan ini juga menguji pemahaman Event Loop.
Prediksi outputnya:
function a() {
console.log('Warlock');
}
function b() {
console.log('Druid');
Promise.resolve().then(() => {
console.log('Rogue');
});
}
function c() {
console.log('Mage');
}
function d() {
setTimeout(c, 100);
const temp = Promise.resolve().then(a);
console.log('Warrior');
setTimeout(b, 0);
}
d();
Pahami urutan di d()β
function d() {
setTimeout(c, 100); // 4. macro task (delay 100ms)
const temp = Promise.resolve().then(a); // 2. micro task (setelah kode sync)
console.log('Warrior'); // 1. kode sync
setTimeout(b, 0); // 3. macro task (0ms, tetap macro)
}
Urutan eksekusi:
- Kode sinkron:
console.log('Warrior')->Warrior - Micro task:
Promise.resolve().then(a)-> jalankana()->Warlock - Macro task:
setTimeout(b, 0)berjalan duluan- jalankan
b()->Druid - di dalam
b,Promise.resolve().then(...)adalah micro task ->Rogue
- Macro task:
setTimeout(c, 100)berjalan kemudian ->Mage
Jawabanβ
Warrior
Warlock
Druid
Rogue
Mage
Konsep Kunciβ
- Kode sync > Micro task (
Promise) > Macro task (setTimeout) - Callback
.then()adalah micro task: berjalan setelah macro task saat ini, sebelum macro task berikutnya setTimeout(..., 0)tetap macro task dan berjalan setelah micro task
Pertanyaan 3: perilaku sinkron vs asinkron konstruktor Promiseβ
Prediksi outputnya:
function printing() {
console.log(1);
setTimeout(function () {
console.log(2);
}, 1000);
setTimeout(function () {
console.log(3);
}, 0);
new Promise((resolve, reject) => {
console.log(4);
resolve(5);
}).then((foo) => {
console.log(6);
});
console.log(7);
}
printing();
// output ?
Detail pentingβ
Poin kunci: kode di dalam konstruktor Promise berjalan secara sinkron.
Hanya callback .then() / .catch() yang bersifat asinkron.
Analisis eksekusi:
console.log(1); // 1. sync
setTimeout(() => console.log(2), 1000); // 5. macro task (1000ms)
setTimeout(() => console.log(3), 0); // 4. macro task (0ms)
new Promise((resolve, reject) => {
console.log(4); // 2. sync (di dalam konstruktor)
resolve(5);
}).then((foo) => {
console.log(6); // micro task
});
console.log(7); // 3. sync
Alur eksekusi:
- Sync: 1 -> 4 -> 7
- Micro task: 6
- Macro task (berdasarkan delay): 3 -> 2
Jawabanβ
1
4
7
6
3
2
Konsep Kunciβ
- Body konstruktor Promise bersifat sinkron:
console.log(4)bukan async. - Hanya
.then()dan.catch()yang merupakan micro task asinkron. - Urutan: kode sync -> micro task -> macro task.
Jebakan Umum (Common Pitfalls)β
1. Lupa returnβ
Jika Anda lupa return dalam chain Promise, .then() berikutnya menerima undefined.
// β salah
fetchUser()
.then((user) => {
fetchPosts(user.id); // lupa return
})
.then((posts) => {
console.log(posts); // undefined
});
// β
benar
fetchUser()
.then((user) => {
return fetchPosts(user.id);
})
.then((posts) => {
console.log(posts); // data yang benar
});
2. Lupa menangkap errorβ
Promise rejection yang tidak tertangani bisa merusak alur dan membuat error runtime yang berisik.
// β bisa menyebabkan unhandled rejection
fetchData()
.then((data) => {
return processData(data);
})
.then((result) => {
console.log(result);
});
// β
tambahkan catch
fetchData()
.then((data) => {
return processData(data);
})
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error('Terjadi error:', error);
});
3. Penggunaan berlebihan new Promise(...)β
Jangan membungkus fungsi yang sudah mengembalikan Promise.
// β pembungkusan yang tidak perlu
function fetchData() {
return new Promise((resolve, reject) => {
fetch(url)
.then((response) => resolve(response))
.catch((error) => reject(error));
});
}
// β
kembalikan langsung
function fetchData() {
return fetch(url);
}
4. Merangkai beberapa catch secara tidak benarβ
Setiap catch() menangani error dari bagian sebelumnya dalam chain.
Promise.resolve()
.then(() => {
throw new Error('Error 1');
})
.catch((e) => {
console.log('Tertangkap:', e.message); // Tertangkap: Error 1
})
.then(() => {
throw new Error('Error 2');
})
.catch((e) => {
console.log('Tertangkap:', e.message); // Tertangkap: Error 2
});
Topik Terkait (Related Topics)β
- async/await - syntax sugar Promise yang lebih bersih
- Event Loop - pemahaman model asinkron yang lebih dalam