Sabtu, 11 Juni 2022

Apa itu Promise di Javascript


Promise merupakan salah satu fitur penting dari ES6. Promise ini dapat menggantikan peran callback dengan menggunakan ciri khas fungsi .then-nya itu. Tapi mengapa fitur ini dinamakan dengan “Promise” alias “Janji”?

Fitur ini berfungsi seperti namanya, yaitu untuk membuat janji. Mari kita analogikan kembali dalam dunia nyata. Ketika kita memesan kopi kepada pelayan, maka secara tidak langsung pelayan tersebut berjanji kepada kita untuk membuatkan kopi kemudian menghidangkannya pada kita. Namun janji bisa hanya tinggal janji. Dalam dunia nyata pun, janji bisa juga tidak terpenuhi, entah itu karena kopi pesanan kita sedang kosong, atau mesin pembuat kopi itu sedang rusak.
Nah, Promise memiliki perilaku yang sama dengan analogi yang digambarkan tadi. Dalam promise terdapat 3 (tiga) kondisi, yakni:
  • Pending (Janji sedang dalam proses)
  • Fulfilled (Janji terpenuhi)
  • Rejected (Janji gagal terpenuhi)
Lantas bagaimana cara membuat janji (Promise) di JavaScript? 

Constructuring a Promise Object

Situs MDN mengatakan Promise merupakan sebuah objek yang digunakan untuk membuat sebuah perhitungan (kode) ditangguhkan dan berjalan secara asynchronous. Untuk membuat objek promise, kita gunakan keyword new diikuti dengan constructor dari Promise:


  1. const coffee = new Promise();


Namun jika kita jalankan kode tersebut, akan mengakibatkan eror seperti ini:


  1. /* ERROR: Promise resolver undefined is not a function */


Di dalam constructor Promise kita perlu menetapkan resolver function atau bisa disebut executor function di mana fungsi tersebut akan dijalankan secara otomatis ketika constructor Promise dipanggil.
  1. const executorFunction = (resolve, reject) => {
  2.  const isCoffeeMakerReady = true;
  3.  if(isCoffeeMakerReady) {
  4.    resolve("Kopi berhasil dibuat");
  5.  } else {
  6.    reject("Mesin Kopi tidak bisa digunakan!")
  7.  }
  8. }
  9.  
  10. const makeCoffee = new Promise(executorFunction);
  11. console.log(makeCoffee);
  12.  
  13. /* output:
  14. Promise { 'Kopi berhasil dibuat' }
  15. */

Executor function dapat memiliki dua parameter, yang berfungsi sebagai resolve() dan reject() function. Berikut penjelasan detailnya:
  • resolve() merupakan parameter pertama pada executor function. Parameter ini merupakan fungsi yang dapat menerima satu parameter, biasanya kita gunakan untuk mengirimkan data ketika promise berhasil dilakukan. Ketika fungsi ini terpanggil, kondisi Promise akan berubah dari pending menjadi fulfilled.
  • reject() merupakan parameter kedua pada executor function. Parameter ini merupakan fungsi yang dapat menerima satu parameter yang digunakan untuk memberikan alasan mengapa Promise tidak dapat terpenuhi. Ketika fungsi ini terpanggil, kondisi Promise akan berubah dari pending menjadi rejected.
Executor function akan berjalan secara asynchronous hingga akhirnya kondisi Promise berubah dari pending menjadi fulfilled/rejected.
Pada contoh kode di atas, berikut ini outputnya:
  1. /* output:
  2. Promise { 'Kopi berhasil dibuat' }
  3. */

Kenapa demikian? Executor function mengeksekusi resolve() dengan membawa data string “Kopi berhasil dibuat”. Coba kita ubah nilai dari variabel isCoffeeMakerReady menjadi false, maka executor function akan mengeksekusi reject() dengan membawa pesan rejection "Mesin Kopi tidak bisa digunakan!". 


  1. /* output:

  2. Promise { <rejected> 'Mesin Kopi tidak bisa digunakan!' }

  3. */


Untuk mudah menggambarkan alurnya, silakan lihat ilustrasi berikut:
202203121723193c4c48c4709d9a0131c14367f44675d0.png
Dari output yang dihasilkan baik ketika fulfilled ataupun rejected masih berupa Promise, bukan nilai yang dibawa oleh fungsi resolve atau reject itu sendiri. Lantas bagaimana cara untuk mengakses nilai yang dibawa oleh fungsi-fungsi tersebut? Caranya adalah dengan menggunakan method .then() yang tersedia pada objek Promise.
onFulfilled and onRejected Functions
Untuk menangani nilai yang dikirimkan oleh resolve() ketika Promise onFulfilled, kita gunakan method .then() pada objek promise yang kita buat. Lalu di dalam method .then() kita berikan parameter berupa function yang berguna sebagai handle callback. Contohnya seperti ini:
  1. const executorFunction = (resolve, reject) => {
  2.  const isCoffeeMakerReady = true;
  3.   if(isCoffeeMakerReady) {
  4.     resolve("Kopi berhasil dibuat");
  5.   } else {
  6.     reject("Mesin Kopi tidak bisa digunakan!")
  7.   }
  8. }
  9.  
  10. const handlerSuccess = resolvedValue => {
  11.  console.log(resolvedValue);
  12. }
  13.  
  14. const makeCoffee = new Promise(executorFunction);
  15. makeCoffee.then(handlerSuccess)
  16.  
  17. /* output:
  18. Kopi berhasil dibuat
  19. */

Mari kita bedah kode yang ada di atas:
  • makeCoffee merupakan objek promise yang akan menghasilkan resolve() dengan membawa nilai ‘Kopi berhasil dibuat’.
  • Lalu kita mendeklarasikan fungsi handlerSuccess() yang mencetak nilai dari parameternya.
  • Kemudian kita memanggil method .then() dari makeCoffee dan memberikan handlerSuccess sebagai parameternya.
  • Ketika makeCoffee terpenuhi (fulfilled), maka handlerSuccess() akan terpanggil dengan parameter nilai yang dibawa oleh resolve(). Sehingga output akan menghasilkan “Kopi berhasil dibuat”.
Namun bagaimana jika objek promise menghasilkan kondisi rejected? Bagaimana cara menangani nilai yang dikirimkan oleh reject()?
Kita dapat menyimpan kedua handle callback baik onFulfilled atau onRejected pada parameter method .then(), yang perlu kita perhatikan adalah pastikan handle callback untuk onFulfilled disimpan pada parameter pertama seperti ini:


  1. makeCoffee.then(handlerSuccess, handlerRejected);


Dengan begitu kita tetap dapat menangani objek promise meskipun dalam kondisi rejected.
  1. const executorFunction = (resolve, reject) => {
  2.  const isCoffeeMakerReady = false;
  3.  if(isCoffeeMakerReady) {
  4.    resolve("Kopi berhasil dibuat");
  5.  } else {
  6.    reject("Mesin Kopi tidak bisa digunakan!")
  7.  }
  8. }
  9.  
  10. const handlerSuccess = coffee => {
  11.  console.log(coffee);
  12. }
  13.  
  14. const handlerRejected = rejectionReason => {
  15.  console.log(rejectionReason)
  16. }
  17.  
  18. const makeCoffee = new Promise(executorFunction);
  19. makeCoffee.then(handlerSuccess, handlerRejected);
  20.  
  21. /* output:
  22. Mesin Kopi tidak bisa digunakan!

onRejected with Catch Method

Salah satu cara menulis kode yang baik adalah mengikuti prinsip yang disebut separation of concerns yang artinya pemisahan masalah. Pemisahan masalah berarti mengorganisasikan kode ke dalam bagian-bagian yang berbeda berdasarkan tugas tertentu. 
Hal ini akan memudahkan kita kelak mencari kode yang salah jika aplikasi tidak bekerja dengan baik.
Perlu diketahui bahwa method .then() akan mengembalikan nilai promise yang sama dengan ketika objek promise itu dipanggil. 
Melalui sifatnya ini, daripada kita menetapkan logika resolve dan reject pada satu method .then(), kita dapat memisahkan kedua logika tersebut menggunakan masing-masing method .then() seperti ini:
  1. const makeCoffee = new Promise(executorFunction);
  2. makeCoffee
  3.  .then(handlerSuccess)
  4.  .then(null, handlerRejected);
  5.  
  6. /* output:
  7. Mesin Kopi tidak bisa digunakan!
  8. */
Namun untuk menetapkan rejected handler kita perlu memberikan nilai null pada parameter method .then(). Well.. hal ini sedikit merepotkan bukan? Solusinya kita dapat menggunakan method lain, yakni .catch().
Method .catch() mirip seperti .then(). Namun method ini hanya menerima satu parameter function yang digunakan untuk rejected handler. Method .catch() ini akan terpanggil bilamana objek promise memiliki kondisi onRejected. Berikut contoh penggunaan dari method .catch():
  1. const makeCoffee = new Promise(executorFunction);
  2. makeCoffee
  3.  .then(handlerSuccess)
  4.  .catch(handlerRejected);
  5.  
  6. /* output:
  7. Mesin Kopi tidak bisa digunakan!
  8. */

Dengan menggunakan method catch(), kita dapat menerapkan prinsip separation of concerns sekaligus membuat kodenya lebih rapi.

Chaining Promise

Kita sudah tahu buruknya penulisan callback hell. Namun kita juga tidak dapat menghindari keadaan di mana asynchronous operation saling bergantungan satu sama lain. Untuk menghindari penulisan callback hell, promise jadi salah satu solusinya.
Dengan promise kita dapat melakukan proses asynchronous operation secara berantai. Contohnya, Ketika kita memesan kopi robusta, maka tahapan yang dilalui oleh barista adalah memastikan biji kopi tersedia, membuat kopi, lalu menghidangkannya kepada pelanggan. Tahapan tersebut harus barista lakukan secara berurutan.
Bagaimana cara melakukan proses berantai pada Promise? Kita bisa melakukannya dengan cara seperti ini:
  1. function reserveACoffee(type, miligrams) {
  2.  getSeeds(type, miligrams)
  3.  .then(makeCoffee)
  4.  .then(servingToTable)
  5.  .then(resolvedValue => {
  6.    console.log(resolvedValue);
  7.  })
  8.  .catch(rejectedReason => {
  9.    console.log(rejectedReason);
  10.  })
  11. }
  12.  
  13. reserveACoffee("liberica", 80);
  14.  
  15. /* output:
  16. Pesanan kopi sudah selesai!
  17. */
Mengapa bisa seperti itu? Mari kita bedah kodenya satu persatu.
Ketika kita memesan kopi melalui fungsi reserveACoffee(), pertama barista akan mengambil biji kopi melalui fungsi getSeeds(). Fungsi ini membutuhkan 2 (dua) parameter yaitu type (tipe kopi), dan miligrams (banyak kopi yang diperlukan). 
Fungsi ini mengembalikan objek promise, di mana jika biji yang dipesan tersedia akan mengembalikan resolve -> “Biji kopi didapatkan!”. Namun jika biji kopi tidak tersedia, maka akan mengembalikan reject -> “Maaf stok kopi habis!”. Berikut kode dari fungsi getSeeds:
  1. const getSeeds = (type, miligrams) => {
  2.  return new Promise((resolve, reject) => {
  3.    if(state.seedStocks[type] >= miligrams) {
  4.      state.seedStocks[type] -= miligrams;
  5.      resolve("Biji kopi didapatkan!")
  6.    } else {
  7.      reject("Maaf stock kopi habis!")
  8.    }
  9.  });
  10. }

Lalu kita panggil method .then() dari fungsi getSeeds, dan memberikan parameter fungsi makeCoffee di dalamnya. Fungsi makeCoffee() akan menerima parameter berupa nilai yang dibawa resolve pada getSeeds()
Fungsi ini juga mengembalikan nilai promise juga, di mana jika mesin kopi siap digunakan, maka akan mengembalikan resolve -> “Kopi berhasil dibuat”. Namun jika sebaliknya, mesin kopi tidak siap untuk digunakan, maka akan mengembalikan reject -> “Maaf mesin tidak dapat digunakan!”. Berikut kode dari fungsi makeCoffee:
  1. const makeCoffee = seeds => {
  2.  return new Promise((resolve, reject) => {
  3.    if(state.isCoffeeMakerReady) {
  4.      resolve("Kopi berhasil dibuat!")
  5.    } else {
  6.      reject("Maaf mesin tidak dapat digunakan!");
  7.    }
  8.  })
  9. }

Setelah kita mendapatkan kopi dari fungsi makeCoffee. Lalu kopi tersebut dihidangkan dengan menggunakan fungsi servingToTable. Fungsi ini juga mengembalikan promise dengan resolve yang membawa nilai “Pesanan kopi sudah selesa!”.
  1. const servingToTable = coffee => {
  2.  return new Promise(resolve => {
  3.    resolve("Pesanan kopi sudah selesai!")
  4.  })
  5. }

Lalu kita gunakan method .then() yang terakhir untuk mencetak nilai yang dikembalikan oleh fungsi servingToTable.
Kemudian yang paling terakhir adalah memanggil method .catch(). Di mana method ini akan menangani promise rejection yang terjadi. Entah itu disebabkan oleh biji kopi yang dipesan habis, ataupun mesin kopi tidak dapat digunakan.
Menjelaskan kode melalui skenario terkadang sulit untuk dimengerti. Sebagian dari kita mungkin lebih nyaman membaca keseluruhan kodenya secara langsung. Berikut contoh kode dalam penerapan chaining promise berdasarkan skenario di atas.
  1. const state = {
  2.  isCoffeeMakerReady: true,
  3.  seedStocks: {
  4.    arabica: 250,
  5.    robusta: 60,
  6.    liberica: 80
  7.  }
  8. }
  9.  
  10. const getSeeds = (type, miligrams) => {
  11.  return new Promise((resolve, reject) => {
  12.    if(state.seedStocks[type] >= miligrams) {
  13.      state.seedStocks[type] -= miligrams;
  14.      resolve("Biji kopi didapatkan!")
  15.    } else {
  16.      reject("Maaf stock kopi habis!")
  17.    }
  18.  });
  19. }
  20.  
  21. const makeCoffee = seeds => {
  22.  return new Promise((resolve, reject) => {
  23.    if(state.isCoffeeMakerReady) {
  24.      resolve("Kopi berhasil dibuat!")
  25.    } else {
  26.      reject("Maaf mesin tidak dapat digunakan!");
  27.    }
  28.  })
  29. }
  30.  
  31. const servingToTable = coffee => {
  32.  return new Promise(resolve => {
  33.    resolve("Pesanan kopi sudah selesai!")
  34.  })
  35. }
  36.  
  37. function reserveACoffee(type, miligrams) {
  38.  getSeeds(type, miligrams)
  39.  .then(makeCoffee)
  40.  .then(servingToTable)
  41.  .then(resolvedValue => {
  42.    console.log(resolvedValue);
  43.  })
  44.  .catch(rejectedReason => {
  45.    console.log(rejectedReason);
  46.  })
  47. }
  48.  
  49. /* Silakan ubah tipe kopi dan kuantitasnya, untuk mendapatkan promise rejection*/
  50. reserveACoffee("liberica", 80);

Anda bisa mencoba dengan menjalankan kodenya melalui platform online melalui tautan berikut: https://repl.it/@dicodingacademy/163-02-chaining-promise?lite=true

Promise All

Materi sebelumnya kita belajar bagaimana promise dapat menangani situasi di mana terdapat asynchronous process yang saling membutuhkan untuk melaksanakan tugasnya. Namun bagaimana jika kita ingin menjalankan banyak promise sekaligus namun tidak memperdulikan urutan?
Kita sering pergi ke cafe untuk menikmati secangkir kopi bersama teman kuliah ataupun rekan kerja. Ketika memesan kopi, biasanya kita lakukan secara bersamaan. Meskipun kopi yang kita pesan berbeda, tak jarang pelayan menghidangkannya berbarengan dengan kopi yang teman kita pesan. Nah pada kasus inilah pelayan menggunakan teknik Promise.all().
Method Promise.all() dapat menerima banyak promise (dalam bentuk array) pada parameternya. Kemudian method tersebut akan mengembalikan nilai seluruh hasil dari promise yang kita tetapkan dalam bentuk array.
Contohnya seperti berikut ini:
  1. const arabicaOrder = () => {
  2. return new Promise(resolve => {
  3. setTimeout(() => {
  4. resolve("Kopi arabika selesai!")
  5. }, 4000)
  6. })
  7. }
  8.  
  9. const robustaOrder = () => {
  10. return new Promise(resolve => {
  11. setTimeout(() => {
  12. resolve("Kopi robusta selesai!")
  13. }, 2000)
  14. })
  15. }
  16.  
  17. const libericaOrder = () => {
  18. return new Promise(resolve => {
  19. setTimeout(() => {
  20. resolve("Kopi liberica selesai!")
  21. }, 3000)
  22. })
  23. }
  24.  
  25. const promises = [arabicaOrder(), robustaOrder(), libericaOrder()];
  26.  
  27. Promise.all(promises)
  28. .then(resolvedValue => {
  29. console.log(resolvedValue);
  30. })
  31.  
  32. /* output:
  33. [ 'Kopi arabika selesai!',
  34. 'Kopi robusta selesai!',
  35. 'Kopi liberica selesai!' ]
  36. */

Ketika kode di atas dieksekusi, kita perlu menunggu 4 detik sebelum akhirnya output pada console ditampilkan. Ini menunjukan bahwa Promise.all() akan mengembalikan nilai jika seluruh proses promise di dalamnya selesai dijalankan.
Urutan nilai yang dihasilkan oleh method ini sesuai dengan urutan promise yang kita tentukan pada parameternya
  1. const promises = [arabicaOrder(), robustaOrder(), libericaOrder()];
  2.  
  3. Promise.all(promises)
  4. .then(resolvedValue => {
  5.  console.log(resolvedValue);
  6. })

Nilai kopi arabika akan tetap berada di posisi pertama, meskipun proses pembuatannya memakan waktu paling lama. 

Contoh tentang setTimeout dan Callback Function yang biasa dipakai untuk promise dan masih banyak lagi.


setTimeout

Fungsi setTimeout() merupakan cara yang paling mudah untuk membuat kode kita dijalankan secara asynchronous. Fungsi ini menerima dua buah parameter. Pertama adalah fungsi yang akan dijalankan secara asynchronous, dan kedua adalah nilai number dalam milisecond sebagai nilai tunggu sebelum fungsi dijalankan. Contoh penggunaannya adalah seperti ini:


  1. console.log("Selamat datang!");

  2. setTimeout(() => {

  3.  console.log("Terimakasih sudah mampir, silakan datang kembali!")

  4. }, 3000)

  5. console.log("Ada yang bisa dibantu?")


Jika kita hanya mengenal program secara synchronous, maka kita dapat membayangkan hasilnya memiliki urutan sebagai berikut:
  • Mencetak -> Selamat datang!
  • Menunggu selama tiga detik
  • Mencetak -> Terima kasih sudah mampir, silakan datang kembali!
  • Mencetak -> Ada yang bisa dibantu?
Namun nyatanya setTimeout() tidak akan menghentikan JavaScript untuk melakukan eksekusi kode pada baris berikutnya. Sehingga urutannya menjadi seperti berikut:
  • Mencetak -> Selamat datang!
  • Mencetak -> Ada yang bisa dibantu?
  • Menunggu selama tiga detik
  • Mencetak -> Terimakasih sudah mampir, silakan datang kembali!
Jika kode tersebut dijalankan, maka akan menampilkan output sebagai berikut:
20220312170729f414a0fada93abd3a5d0736d14ecf72b.gif

Callback Function

Hal yang membuat bingung ketika kita bekerja dengan synchronous dan asynchronous program adalah bagaimana menangani suatu nilai yang didapatkan secara asynchronous pada program yang berjalan secara synchronous. Contohnya seperti kode berikut:

  1. const getCake = () => {
  2.  let cake = null;
  3.  console.log("Sedang membuat kue, silakan tunggu ....")
  4.  setTimeout(() => {
  5.    cake = "Kue Selesai!"
  6.  }, 3000)
  7.  return cake;
  8. }
  9.  
  10. const cake = getCake();
  11. console.log(cake);
  12.  
  13. /*output:
  14. Sedang membuat kue, silakan tunggu ....
  15. null
  16. */


Jika kita melakukan hal seperti ini untuk mencetak nilai cake yang sesungguhnya, maka hal tersebut tidak akan pernah terjadi. Karena seperti yang sudah kita ketahui fungsi setTimeout() tidak akan menghentikan JavaScript untuk mengeksekusi kode yang ada selanjutnya. Jadi fungsi getCake() akan selalu mengembalikan nilai null, karena kode return cake akan dieksekusi terlebih dahulu dibandingkan dengan cake = “Kue Selesai!”. Kode asynchronous perlu disusun dengan cara yang berbeda dari synchronous code. Cara paling dasar adalah dengan menggunakan callback function.
Apa itu callback function? Mari kita bayangkan kembali melalui pandangan dunia nyata. Kita menunggu pesanan kopi datang di meja kita tapi , pada saat itu juga kita tidak bisa berada terus di tempat itu karena ada urusan mendadak. Pada kasus ini mungkin terdapat dua aksi yang bisa kita lakukan:
  • (Synchronous) Kita tetap menunggu di meja hingga kopi itu datang dan kemudian meninggalkan kedai kopi.
  • (Asynchronous) Kita meminta tolong kepada teman kita untuk menerima kopi itu, dan bertemu nanti untuk memberikan kopinya. Sehingga kita tidak perlu menunggu untuk meninggalkan kedai kopi.
Nah pada JavaScript, teman kita berperan layaknya callback function. Ia diperintahkan pada sebuah fungsi asynchronous kemudian akan dipanggil/digunakan ketika tugas itu selesai.
Bagaimana cara melakukannya? Yang pertama kita tambahkan parameter dengan nama callback pada fungsi asynchronous.
  1. const getCake = callback => {
  2.  let cake = null;
  3.  console.log("Sedang membuat kue, silakan tunggu ....")
  4.  setTimeout(() => {
  5.    cake = "Kue Selesai!";
  6.  }, 3000)
  7.  return cake;
  8. }

Kemudian kita panggil/gunakan callback yang diisikan dengan data yang akan dibawa (cake) ketika task selesai dilakukan.

  1. setTimeout(function() {
  2.    cake = "Kue Selesai!";
  3.    callback(cake);
  4. }, 3000)

Setelah menggunakan callback, fungsi tidak perlu lagi mengembalikan nilai. Sehingga kita bisa hapus kode return cake;. Sehingga keseluruhan fungsi akan tampak seperti ini:

  1. const getCake = callback => {
  2.  let cake = null;
  3.  console.log("Sedang membuat kue, silakan tunggu ....")
  4.  setTimeout(() => {
  5.    cake = "Kue Selesai!";
  6.    callback(cake);
  7.  }, 3000)
  8. }


Kemudian untuk menggunakan fungsi getCake, kita ubah kode dari:


  1. const cake = getCake();

  2. console.log(cake);



Menjadi:


  1. getCake(cake => {

  2.  console.log(cake);

  3. })



Sehingga ketika dijalankan akan sesuai dengan harapan kita.
  1. const getCake = callback => {
  2.  let cake = null;
  3.  console.log("Sedang membuat kue, silakan tunggu ....")
  4.  setTimeout(() => {
  5.    cake = "Kue Selesai!";
  6.    callback(cake);
  7.  }, 3000)
  8. }
  9.  
  10. getCake(cake => {
  11.  console.log(cake);
  12. })
  13.  
  14. /* output:
  15. Sedang membuat kue, silakan tunggu ....
  16. ---- setelah 3 detik ----
  17. Kue Selesai!
  18. */


Callback Hell

Kita sudah mengetahui bahwa callback dibutuhkan untuk mendapatkan nilai yang berasal dari asynchronous function. Lantas bagaimana jika terdapat proses satu sama lain yang saling bergantungan? Contohnya, untuk membuat kue tahapan yang perlu kita lakukan adalah:
  1. Mempersiapkan bahan
  2. Membuat adonan
  3. Menyiapkan adonan ke cetakan
  4. Memanggang adonan
Tahapan tersebut sangat tergantung satu sama lain. Kita tidak bisa memanggang adonan sebelum membuat adonannya, dan kita tidak bisa membuat adonan tanpa mempersiapkan bahannya terlebih dahulu. 
Jika seluruh tahapan tersebut berjalan secara synchronous, mungkin kita bisa melakukanya seperti ini:
  1. function makeACake(...rawIngredients) {
  2.  const ingredients = gatheringIngredients(rawIngredients),
  3.  dough = makeTheDough(ingredients),
  4.  pouredDough = pourDough(dough),
  5.  cake = bakeACake(pourDough),
  6.  console.log(cake);
  7. }

Namun jika fungsi-fungsi tersebut berjalan secara asynchronous, maka kita akan membuat yang namanya callback hell. Callback hell terjadi karena banyak sekali callback function yang bersarang karena saling membutuhkan satu sama lain, sehingga kode akan tampak seperti ini:

  1. function makeACake(...rawIngredients) {
  2.  gatheringIngredients(rawIngredients, function(ingridients) {
  3.    makeTheDough(ingridients, function(dough) {
  4.      pourDough(dough, function(pouredDough) {
  5.        bakeACake(pouredDough, function(cake) {
  6.          console.log(cake);
  7.        });
  8.      });
  9.    });
  10.  });
  11. }


Melihat kode seperti ini saja, kepala jadi pusing. Terbayang sulitnya memelihara kode ini di masa yang akan datang.
Lantas apa solusi agar kita dapat menghindari callback hell? Salah satunya adalah dengan menggunakan Promise.
  1. function makeACake(...rawIngredients) {
  2.  gatheringIngredients(rawIngredients)
  3.  .then(makeTheDough)
  4.  .then(pourDough)
  5.  .then(bakeACake)
  6.  .then(console.log);
  7. }
Dengan Promise, kita dapat meminimalisir callback hell dan mengubahnya menjadi kode yang sangat mudah dibaca. Bahkan dengan kode seperti itu, non-developer pun dapat mengerti apa maksud dari kode tersebut. 

Solution : Promise

Apakah Anda sudah berhasil mengubah penerapan callback function menjadi Promise? Jika belum, mari kita lakukan bersama-sama.
Langkah awal, pada class DataSource. Kita hapus constructor yang memuat callback function pada class.

  1. class DataSource {

  2.     constructor(onSuccess, onFailed) {

  3.         this.onSuccess = onSuccess;

  4.         this.onFailed = onFailed;

  5.     }

  6.  

  7.     searchClub(keyword) {

  8.         const filteredClubs = clubs.filter(club => club.name.toUpperCase().includes(keyword.toUpperCase()));

  9.  

  10.         if (filteredClubs.length) {

  11.             this.onSuccess(filteredClubs);

  12.         } else {

  13.             this.onFailed(`${keyword} is not found"`);

  14.         }

  15.     }

  16. }



Sehingga class DataSource akan tampak seperti ini:

  1. class DataSource {

  2.     searchClub(keyword) {

  3.         const filteredClubs = clubs.filter(club => club.name.toUpperCase().includes(keyword.toUpperCase()));

  4.  

  5.         if (filteredClubs.length) {

  6.             this.onSuccess(filteredClubs);

  7.         } else {

  8.             this.onFailed(`${keyword} is not found"`);

  9.         }

  10.     }

  11. }



Karena kita sudah menghapus callback function beserta constructor class-nya, kita tidak perlu membuat instance dari class DataSource. Kita buat method searchClub dapat diakses secara langsung dari DataSource tanpa harus membuat instance
Masih ingat bagaimana cara melakukannya? Untuk membuat method dalam class dapat diakses tanpa membuat instance, kita perlu menambahkan keyword static sebelum membuat method-nya.

  1. class DataSource {

  2.     static searchClub(keyword) {

  3.         const filteredClubs = clubs.filter(club => club.name.toUpperCase().includes(keyword.toUpperCase()));

  4.  

  5.         if (filteredClubs.length) {

  6.             this.onSuccess(filteredClubs);

  7.         } else {

  8.             this.onFailed(`${keyword} is not found"`);

  9.         }

  10.     }

  11. }



Setelah itu, kita buat method searchClub mengembalikan nilai promise seperti ini:

  1. class DataSource {

  2.     static searchClub(keyword) {

  3.         return new Promise((resolve, reject) => {

  4.             const filteredClubs = clubs.filter(club => club.name.toUpperCase().includes(keyword.toUpperCase()));

  5.             if (filteredClubs.length) {

  6.                 this.onSuccess(filteredClubs);

  7.             } else {

  8.                 this.onFailed(`${keyword} is not found"`);

  9.             }

  10.         });

  11.     }

  12. }



Jangan lupa ubah juga pemanggilan callback onSuccess() dan onFailed(), menggunakan resolve() dan reject(). Maka sekarang class DataSource akan tampak seperti ini:

  1. class DataSource {

  2.     static searchClub(keyword) {

  3.         return new Promise((resolve, reject) => {

  4.             const filteredClubs = clubs.filter(club => club.name.toUpperCase().includes(keyword.toUpperCase()));

  5.             if (filteredClubs.length) {

  6.                 resolve(filteredClubs);

  7.             } else {

  8.                 reject(`${keyword} is not found"`);

  9.             }

  10.         });

  11.     }

  12. }



Namun belum sampai di sini. Dengan mengubah method searchClub menjadi static dan mengubah callback menjadi Promise, tentu kita perlu penyesuaian kembali ketika method itu digunakan.
Pada berkas main.js, ubah fungsi onButtonSearchClicked ini:

  1. const onButtonSearchClicked = () => {

  2.         const dataSource = new DataSource(renderResult, fallbackResult);

  3.         dataSource.searchClub(searchElement.value);

  4. };



Menjadi seperti ini:

  1. const onButtonSearchClicked = () => {

  2.         DataSource.searchClub(searchElement.value)

  3.             .then(renderResult)

  4.             .catch(fallbackResult)

  5. };



Atau jika Anda lebih suka menggunakan async/await, Anda bisa membuat fungsi onButtonSearchClicked berjalan secara asynchronous seperti ini:

  1. const onButtonSearchClicked = async () => {

  2.    try{

  3.        const result = await DataSource.searchClub(searchElement.value);

  4.        renderResult(result);

  5.    } catch (message) {

  6.        fallbackResult(message)

  7.    }

  8. };


Promise sudah berhasil diterapkan! Untuk memastikan perubahan yang kita lakukan sudah benar, silakan Anda coba buka berkas index.html pada browser. Jika aplikasi berjalan dengan lancar, maka promise berhasil diterapkan dengan baik.
Namun jika aplikasi tidak berjalan lancar, dan menghasilkan eror pada console browser, jangan malu untuk bertanya pada forum diskusi ya!
Langkah dari solution ini bisa Anda temukan juga pada repository berikut: https://github.com/dicodingacademy/a163-bfwd-labs/tree/107-club-finder-promise-solution

Posting Komentar