Secara default, proses pada JavaScript merupakan proses-proses yang bersifat Synchronous, yaitu kode program dieksekusi dari baris atas sampai ke bawah secara berurutan. Namun ada beberapa fungsi pada JavaScript yang bersifat Asynchronous, salah satunya yaitu setTimeout
setTimeout(function() {
// kode program di dalam sini akan dieksekusi setelah 2000 milidetik (atau 2 detik)
console.log("This is first");
}, 2000);
setTimeout(function() {
// kode program di dalam sini akan dieksekusi setelah 1000 milidetik (atau 1 detik)
console.log("This is second");
}, 1000);
console.log("This is third");
Jika ingin mengeksekusi program di atas secara berurutan, maka kita harus menaruh kode program di dalam callback. Callback merupakan fungsi yang dieksekusi di akhir jalannya suatu fungsi yang lain. Fungsi setTimeout(Callback, Number) menerima parameter pertama berupa callback
setTimeout(function() {
// kode program di dalam sini akan dieksekusi setelah 2000 milidetik (atau 2 detik)
console.log("This is first");
setTimeout(function() {
// kode program di dalam sini akan dieksekusi setelah 1000 milidetik (atau 1 detik)
console.log("This is second");
console.log("This is third");
}, 1000);
}, 2000);
console.log(); // abaikan baris kode ini
Kamu pasti akan menemukan pola kode program yang banyak menggunakan callback seperti ini ketika membuat program dengan Node.js. Jika callback yang digunakan terlalu banyak dan dalam, maka akan terjadi yang disebut sebagai Callback Hell.
Salah satu cara untuk menyelesaikan masalah tersebut adalah dengan menggunakan Promise
Promise merupakan suatu objek yang dapat membuat suatu fungsi yang Asynchronous bersifat menjadi Synchronous dengan menggunakan then. Fungsi lain yang bersifat Asynchronous adalah fetch. fetch merupakan fungsi yang digunakan untuk melakukan network request berdasarkan url yang diberikan. Namun kita dapat menggunakan promise pada fetch karena nilai balikannya berupa promise. Sehingga kita dapat merantainya dengan then
var fetch = require("node-fetch"); // baris kode ini boleh tidak ditulis jika dieksekusi di browser
function do_network_request() {
fetch("https://jsonplaceholder.typicode.com/todos?id=1")
.then(function(response) {
return response.json(); // mengambil response body dalam bentuk json
})
.then(function(json) {
console.log(typeof(json));
console.log(json);
});
}
do_network_request();
Cara lain agar membuat fungsi Asynchronous menjadi Synchronous adalah dengan menggunakan Async/Await. Gunakan keyword async pada fungsi yang ada proses yang bersifat Asynchronous dan berupa promise, lalu tambahkan keyword await pada proses yang nilai balikannya berupa promise tersebut.
async function do_network_request() {
var response = await fetch("https://jsonplaceholder.typicode.com/todos?id=2");
var json = await response.json();
console.log(json);
}
do_network_request();
new Promise((resolve, reject) => {
const error = false;
if (!error) {
resolve('success'); // nilai 'success' akan tersedia pada .then
}
else {
reject('failed'); // nilai 'failed' akan tersedia pada .catch
}
})
.then(result => {
console.log(result);
})
.catch(error => {
console.log(error);
});
// avoiding callback hell with promise chaining
new Promise((resolve, reject) => {
setTimeout(() => {
const counter = 1;
console.log('counter 1');
resolve(counter);
}, 1500);
})
.then(counter => {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('counter 2');
resolve(counter + 1);
}, 1500);
});
})
.then(counter => {
console.log('last counter');
console.log(counter + 1);
});
// conditional promise chaining
function promisedRedirect(isLoggedIn) {
let promiseChain = Promise.resolve();
if (isLoggedIn) {
promiseChain = new Promise((resolve, reject) => {
resolve('redirecting to home page');
});
}
else {
promiseChain = new Promise((resolve, reject) => {
resolve('redirecting to login page');
});
}
return promiseChain;
}
promisedRedirect(false)
.then(res => {
console.log(res);
});
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise1 result');
}, 1000);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise2 result');
}, 2000);
});
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise3 result');
}, 6000);
});
const promises = Promise.all([promise1, promise2, promise3]);
promises
.then(results => {
console.log(results);
});
Promise.all akan langsung mengembalikan nilai error jika salah satu promise yang dieksekusinya error
const promise4 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise4 result');
}, 1000);
});
const promise5 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('promise5 error');
}, 2000);
});
const promise6 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise6 result');
}, 6000);
});
const promises2 = Promise.all([promise4, promise5, promise6]);
promises2
.then(results => {
console.log(results);
})
.catch(error => {
console.log(error); // nilai balikan berupa error yang dikirim dari promise yang error
});
Bedanya dengan Promise.all adalah allSettled akan mengembalikan nilai setelah semua promise selesai dieksekusi meskipun terjadi error pada salah satu promise-nya
const promise7 = Promise.resolve(3);
const promise8 = new Promise((resolve, reject) => {
setTimeout(() => {
reject('kurang ganteng');
}, 1000);
});
// allSettled is available on node > 12.x.x
const promises3 = Promise.allSettled([promise7, promise8]);
promises3
.then(results => {
console.log(results);
});