Do you still remember the callback hell from the previous article? Allow me to remind you of that horror.
getData(baseUrl + "/comments/1", (err, data) => {
if (err) {
console.log(err);
} else {
const postId = data.postId;
getData(baseUrl + "/posts/" + postId, (err, data) => {
if (err) {
console.log(err);
} else {
const userId = data.userId;
getData(baseUrl + "/users/" + userId, (err, data) => {
if (err) {
console.log(err);
} else {
document.getElementById("display").innerHTML = JSON.stringify(
data,
null,
4,
);
}
});
}
});
}
});
But don’t worry, because in this article I will discuss how to escape from callback hell by using Promise.
What is a Promise?
Imagine you are ordering food from your favorite restaurant through an online application. When ordering, you don’t immediately get your food; there are several steps that must be taken such as preparing, cooking, and delivering the food to your home. During this process, you can do other things like watching TV, reading books, or working, without having to wait at the door.
In this scenario, the food ordering app gives you a “promise” that your food will arrive. You might also get status updates like “food is being prepared”, “food is being delivered”, and finally “food has arrived”.
Similarly in JavaScript, Promise is a way to manage asynchronous operations. When you create a Promise, it’s like making a promise that some value or result will be available in the future. In practice, a Promise can be in one of three states:
- Pending (Waiting): The state when a Promise is still waiting for the result of an asynchronous process, not yet fulfilled or rejected.
- Fulfilled (Fulfilled): When a Promise has obtained the expected result.
- Rejected (Rejected): When an error occurs and the Promise cannot provide the expected result.
By using Promise, you can write cleaner and more understandable code compared to traditional callbacks. You can use the .then() method to handle successful results and .catch() to handle errors.
Promise was not available from the beginning when Javascript was created. That’s why problems like callback hell emerged, because only the callback pattern was available back then. In 2012 a specification proposal for Promise was created, and only in 2015 was it formally standardized in ECMAScript2015, then implemented.
How to handle Promise?
Basically a Promise is an object that stores a value that is promised to exist at a certain time. Promise has 3 instance methods:
.then(), a method that will run if the Promise status has been fulfilled, and will execute the callback function inside it with arguments containing the value of the Promise. The.then()method can accept 2 arguments, namely fulfilled and rejected callbacks..catch(): A method that will run if the Promise status has been rejected, and will execute the callback function inside it with arguments containing the reason for rejection..finally(): A method that will run when all.then()and.catch()callbacks have finished executing. The callback in this method does not accept any arguments and is used to perform cleanup tasks or other final actions.
The following code is an example of Promise handling using the fetch() API which returns a Promise so it doesn’t block the execution of other functions.
The interconnected pattern between Promise methods is called Chaining. This is possible because every .then() and .catch() method also returns a Promise. The following diagram illustrates the flow of Promise execution.
If you have learned about object-oriented programming in Javascript, you can easily create chaining patterns, look at the following code:
Execution of Promise callbacks inside runtime
In the first article about the introduction to asynchronous programming, the javascript runtime, or the environment where Javascript is run, was discussed.
Promise and setTimeout() both have callbacks that will be called asynchronously in the future. Do they share the same queue space? If yes, then their callbacks will certainly be called in sequence. Let’s prove it, try to guess the output of the following code
After running, it turns out that the output from Promise appears first before setTimeout(). This is because Promise has its own queue called MicroTask queue. The following is a simulation of the previous code in the Javascript runtime.
Javascript Engine
Heap
Call Stack
Event Loop
Web API
API
Task Queue
MicroTask Queue
Memahami urutan task asynchronous pada bahasa Javascript memang cukup menantang, mirip seperti masalah specificity pada CSS. Namun ini sangatlah penting, karena banyak masalah pada pengembangan aplikasi Javascript bisa diselesaikan dengan pemrograman asynchronous.
Membuat Promise-mu sendiri
Membuat sebuah Promise sangatlah mudah (yang sulit memahaminnya 😀). Sebelumnya Saya telah membuat sebuah Promise bernama janjiMakanan yang memiliki nilai terpenuhi 'Selamat makan'.
let janjiMakanan = new Promise((resolve, reject) => {
resolve("Selamat makan");
});
Promise janjiMakanan adalah instance yang dibuat dari promise
constructor. Ia menerima callback yang akan dipanggil dengan 2 buah argumen
berupa fungsi resolve() dan reject(). Penamaan kedua fungsi tersebut
bersifat konvensional, kamu bebas menamainya apa saja. Berikut adalah
kegunaanya:
resolve(nilai)Fungsi ini digunakan untuk mengubah status Promise menjadi fulfilled (terpenuhi) dan mengembalikannilaiyang dijanjikan yang akan ditangani oleh metode.then().reject(alasan): Fungsi ini digunakan untuk mengubah status Promise menjadi rejected (ditolak) dan mengembalikan alasan penolakan. Ketikarejectdipanggil, Promise akan gagal danalasanyang diberikan sebagai argumen ke callback dalam metode.catch().
Ketika membuat instance promise baru, callback yang diberikan sebagai argumen dijalankan secara synchronous. Sehingga jika terjadi proses yang berat akan tetap memblocking eksekusi kode selanjutnya.
Metode statik Promise
konstruktor Promise memiliki beberapa metode statik yang bisa digunakan tanpa perlu membuat sebuah objek Promise (dengan new Promise()). Berikut adalah metode-metode tersebut.
-
Promise.resolve()Mengembalikan sebuah Promise yang langsung terpenuhi dengan nilai yang diberikan. -
Promise.reject()Mengembalikan sebuah Promise yang langsung ditolak dengan alasan yang diberikan. -
Promise.all()Menerima argumen berupa array berisi Promise dan mengembalikan sebuah Promise yang akan terpenuhi jika semua Promise terpenuhi, dan ditolak apabila ada satu Promise yang ditolak. -
Promise.allSettled()Juga menerima argumen berupa array berisi Promise dan mengembalikan sebuah Promise yang akan terpenuhi jika semua Promise telah selesai, tak peduli jika hasilnya terpenuhi atau ditolak. -
Promise.race()Menerima argumen berupa array dan mengembalikan sebuah Promise yang akan terpenuhi ketika satu Promise selesai, baik itu terpenuhi ataupun ditolak. -
Promise.any()Menerima argumen berupa array dan mengembalikan sebuah Promise yang akan terpenuhi ketika satu Promise terpenuhi. Hanya ditolak ketika semua Promise ditolak dan memberikanAggregateError
Aturan-aturan utama Promise
Berikut adalah beberapa aturan utama Promise yang perlu diperhatikan agar dapat menggunakan Promise dengan baik.
- Jika callback mengembalikan Promise lain, maka eksekusi
.then()selanjutnya akan menunggu sampai Promise tersebut terpenuhi (atau ditolak).
- Jika callback pada metode
.then()sebelumnya berupa promise, pastikan untuk melakukan pengembalian, jika tidak, metode.then()selanjutnya tidak dapat melacak keterpenuhan Promise, dan Promise menjadi floating.
- Promise lebih baik ditangani secara flat dibanding nested, karena nanti akan lebih sulit dibaca.
// hindari *nesting*
fetch("url1").then((response1) => {
return fetch("url2").then((response2) => {
//...
});
});
// Pilih bentuk *flat*
fetch("url1")
.then((response1) => fetch("url2"))
.then((response2) => {
//...
});
- Callback yang dijadikan argumen untuk metode
.then()tidak akan pernah dipanggil secara synchronous meski Promise-nya sudah terpenuhi.
console.log("start");
Promise.resolve("sukses").then((hasil) => console.log(hasil));
console.log("end"); // start, end, sukses
- Hanya nilai terpenuhi atau tertolak pertama yang akan dikembalikan oleh suatu Promise.
new Promise((resolve, reject) => {
resolve("1");
resolve("2");
}).then((hasil) => {
console.log(hasil); // '1'
});
Istilah dalam Promise
Banyak istilah (term) terkait Promise yang mirip satu sama lain sehingga membuat bingugn. Terlebih ketika diterjemahkan ke bahasa Indonesia , terkadang ada konteks yang hilang.
Berikut adalah daftar istilah terkait Promise dalam bahasa Inggris, translasi, dan penjelasannya.
| eng | id | keterangan |
|---|---|---|
| pending | menunggu | Keadaan ketika Promise masih menunggu hasil dari suatu proses asynchronous, belum terpenuhi ataupun tertolak. |
| fulfilled | terpenuhi | Ketika Promise sudah mendapatkan hasil dan metode .then() menjalankan callback didalamnya. |
| rejected | ditolak/tertolak | Keadaan saat ada suatu hal yang membuat Promise tidak mendapatkan hasil yang diinginkan (error) dan metode .catch menjalankan callback didalamnya. |
| settled | selesai | bukan merupakan suatu keadaan (state) tertentu, hanya istilah kebahasaan untuk kondisi Promise sudah tidak menunggu, entah terpenuhi ataupun tertolak. |
| resolved | tuntas | Biasanya, Promise yang tuntas ialah Promise yang sudah selesai, alias sudah terpenuhi ataupun tertolak. Namun terkadang sebuah Promise dituntaskan dengan Promise yang lain, maka keadaanya akan mengikuti Promise yang dijadikan argumen penuntasan. |
Jika masih belum klik, kamu bisa juga melihat dengan sudut pandang state and fate (keadaan dan takdir). Menunggu, terpenuhi dan tertolak adalah keadaan dari suatu Promise. Sedangkan tuntas ataupun tidak tuntas (unresolved) adalah takdir dari Promise.
Promise yang tuntas ialah Promise yang apabila di resolve() ataupun reject() sudah tidak berdampak apa-apa, karena Promise tersebut sudah terpenuhi atau tertolak sebelumnya (ingat, Promise hanya bisa di resolve() satu kali).
Dibawah adalah ilustrasi untuk menggambarkan keadaan sebuah Promise.

resolve() memiliki garis ke lingkaran putih dan tidak langsung terpenuhi karena jika resolve() mendapatkan argumen berupa Promise, hasil akhir belum diketahui.
referensi:
Itulah sedikit tentang Promise. Masih banyak detail yang bisa dipelajari tentang Promise dan penanganannya di internet. Setelah ini Saya akan membahas satu hal lagi terkait Promise yaitu async/await.
Referensi
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
- https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide
- https://www.joshwcomeau.com/javascript/promises/#rejected-promises-8
- https://github.com/domenic/promises-unwrapping/blob/master/docs/states-and-fates.md