Janji JavaScript: Tutorial dengan Contoh
Diterbitkan: 2022-03-11Janji adalah topik hangat di lingkaran pengembangan JavaScript, dan Anda pasti harus mengenalnya. Mereka tidak mudah untuk membungkus kepala Anda; dibutuhkan beberapa tutorial, contoh, dan sejumlah latihan yang layak untuk memahaminya.
Tujuan saya dengan tutorial ini adalah untuk membantu Anda memahami Janji JavaScript, dan mendorong Anda untuk berlatih menggunakannya lebih banyak. Saya akan menjelaskan apa itu janji, masalah apa yang mereka pecahkan, dan bagaimana cara kerjanya. Setiap langkah, yang dijelaskan dalam artikel ini, disertai dengan contoh kode jsbin
untuk membantu Anda bekerja, dan digunakan sebagai dasar untuk eksplorasi lebih lanjut.
Apa itu janji JavaScript?
Janji adalah metode yang pada akhirnya menghasilkan nilai. Ini dapat dianggap sebagai mitra asinkron dari fungsi pengambil. Esensinya dapat dijelaskan sebagai:
promise.then(function(value) { // Do something with the 'value' });
Promise dapat menggantikan penggunaan callback yang tidak sinkron, dan menjanjikan beberapa manfaat darinya. Mereka mulai mendapatkan landasan karena semakin banyak perpustakaan dan kerangka kerja merangkul mereka sebagai cara utama mereka untuk menangani asinkronisitas. Ember.js adalah contoh yang bagus dari kerangka kerja semacam itu.
Ada beberapa perpustakaan yang mengimplementasikan spesifikasi Promises/A+. Kita akan mempelajari kosakata dasar, dan bekerja melalui beberapa contoh menjanjikan JavaScript untuk memperkenalkan konsep di baliknya dengan cara yang praktis. Saya akan menggunakan salah satu pustaka implementasi yang lebih populer, rsvp.js, dalam contoh kode.
Bersiaplah, kita akan melempar banyak dadu!
Mendapatkan perpustakaan rsvp.js
Promise, dan dengan demikian rsvp.js, dapat digunakan baik di server maupun di sisi klien. Untuk menginstalnya untuk nodejs , buka folder proyek Anda dan ketik:
npm install --save rsvp
Jika Anda bekerja di front-end dan menggunakan bower, itu hanya
bower install -S rsvp
jauh.
Jika Anda hanya ingin menguasai permainan, Anda dapat memasukkannya melalui tag skrip sederhana (dan dengan jsbin
, Anda dapat menambahkannya melalui tarik-turun “Tambah perpustakaan”):
<script src="//cdn.jsdelivr.net/rsvp/3.0.6/rsvp.js"></script>
Sifat apa yang dimiliki janji?
Janji dapat berada dalam salah satu dari tiga status: tertunda , terpenuhi , atau ditolak . Saat dibuat, janji dalam status tertunda. Dari sini, itu bisa pergi ke keadaan terpenuhi atau ditolak. Kami menyebut transisi ini sebagai resolusi dari janji . Keadaan janji yang diselesaikan adalah keadaan terakhirnya, jadi begitu janji itu dipenuhi atau ditolak, janji itu akan tetap ada.
Cara membuat janji di rsvp.js adalah melalui apa yang disebut konstruktor pengungkap. Jenis konstruktor ini mengambil parameter fungsi tunggal dan segera memanggilnya dengan dua argumen, fulfill
dan reject
, yang dapat mentransisikan janji ke status fulfilled
atau rejected
:
var promise = new RSVP.Promise(function(fulfill, reject) { (...) });
Pola janji JavaScript ini disebut konstruktor pengungkap karena argumen fungsi tunggal mengungkapkan kemampuannya ke fungsi konstruktor, tetapi memastikan bahwa konsumen janji tidak dapat memanipulasi statusnya.
Konsumen janji dapat bereaksi terhadap perubahan statusnya dengan menambahkan handler mereka melalui metode then
. Dibutuhkan fungsi penangan pemenuhan dan penolakan, yang keduanya bisa hilang.
promise.then(onFulfilled, onRejected);
Bergantung pada hasil dari proses resolusi promise, handler onFulfilled
atau onRejected
disebut asynchronously .
Mari kita lihat contoh yang menunjukkan urutan eksekusi:
function dieToss() { return Math.floor(Math.random() * 6) + 1; } console.log('1'); var promise = new RSVP.Promise(function(fulfill, reject) { var n = dieToss(); if (n === 6) { fulfill(n); } else { reject(n); } console.log('2'); }); promise.then(function(toss) { console.log('Yay, threw a ' + toss + '.'); }, function(toss) { console.log('Oh, noes, threw a ' + toss + '.'); }); console.log('3');
Cuplikan ini mencetak output yang mirip dengan berikut ini:
1 2 3 Oh, noes, threw a 4.
Atau, jika kita beruntung, kita melihat:
1 2 3 Yay, threw a 6.
Tutorial menjanjikan ini menunjukkan dua hal.
Pertama, bahwa penangan yang kami lampirkan pada janji memang dipanggil setelah semua kode lain berjalan, secara tidak sinkron.
Kedua, bahwa pawang pemenuhan dipanggil hanya ketika janji dipenuhi, dengan nilai penyelesaiannya (dalam kasus kami, hasil lemparan dadu). Hal yang sama berlaku untuk penangan penolakan.
Merantai janji dan menetes ke bawah
Spesifikasi mensyaratkan bahwa fungsi then
(penangan) juga harus mengembalikan janji, yang memungkinkan rangkaian janji bersama, menghasilkan kode yang terlihat hampir sinkron:
signupPayingUser .then(displayHoorayMessage) .then(queueWelcomeEmail) .then(queueHandwrittenPostcard) .then(redirectToThankYouPage)
Di sini, signupPayingUser
mengembalikan janji, dan setiap fungsi dalam rantai janji dipanggil dengan nilai pengembalian dari penangan sebelumnya setelah selesai. Untuk semua tujuan praktis, ini membuat panggilan bersambung tanpa memblokir utas eksekusi utama.
Untuk melihat bagaimana setiap janji diselesaikan dengan nilai pengembalian item sebelumnya dalam rantai, kita kembali ke melempar dadu. Kami ingin melempar dadu maksimal tiga kali, atau sampai enam yang pertama muncul jsbin:
function dieToss() { return Math.floor(Math.random() * 6) + 1; } function tossASix() { return new RSVP.Promise(function(fulfill, reject) { var n = Math.floor(Math.random() * 6) + 1; if (n === 6) { fulfill(n); } else { reject(n); } }); } function logAndTossAgain(toss) { console.log("Tossed a " + toss + ", need to try again."); return tossASix(); } function logSuccess(toss) { console.log("Yay, managed to toss a " + toss + "."); } function logFailure(toss) { console.log("Tossed a " + toss + ". Too bad, couldn't roll a six"); } tossASix() .then(null, logAndTossAgain) //Roll first time .then(null, logAndTossAgain) //Roll second time .then(logSuccess, logFailure); //Roll third and last time
Saat Anda menjalankan kode contoh janji ini, Anda akan melihat sesuatu seperti ini di konsol:
Tossed a 2, need to try again. Tossed a 1, need to try again. Tossed a 4. Too bad, couldn't roll a six.
Janji yang dikembalikan oleh tossASix
ditolak ketika lemparan bukan enam, sehingga penangan penolakan dipanggil dengan lemparan yang sebenarnya. logAndTossAgain
mencetak hasil tersebut di konsol dan mengembalikan janji yang mewakili lemparan dadu lainnya. Lemparan itu, pada gilirannya, juga ditolak dan dikeluarkan oleh logAndTossAgain
berikutnya.
Namun, terkadang Anda beruntung*, dan Anda berhasil mendapatkan angka enam:
Tossed a 4, need to try again. Yay, managed to toss a 6.
* Anda tidak harus seberuntung itu . Ada peluang ~42% untuk melempar setidaknya satu enam jika Anda melempar tiga dadu.

Contoh itu juga mengajarkan kita sesuatu yang lebih. Lihat bagaimana tidak ada lagi lemparan yang dilakukan setelah pelemparan enam pertama yang berhasil? Perhatikan bahwa semua penangan pemenuhan (argumen pertama dalam panggilan ke then
) dalam rantai adalah null
, kecuali yang terakhir, logSuccess
. Spesifikasi mensyaratkan bahwa jika handler (pemenuhan atau penolakan) bukan fungsi maka janji yang dikembalikan harus diselesaikan (dipenuhi atau ditolak) dengan nilai yang sama. Dalam contoh janji di atas, handler pemenuhan, null
, bukan fungsi dan nilai janji dipenuhi dengan 6. Jadi janji yang dikembalikan oleh panggilan then
(yang berikutnya dalam rantai) juga akan dipenuhi dengan 6 sebagai nilainya.
Ini berulang hingga penangan pemenuhan yang sebenarnya (yang merupakan fungsi) hadir, sehingga pemenuhan mengalir ke bawah hingga ditangani. Dalam kasus kami, ini terjadi di akhir rantai di mana ia dengan riang keluar ke konsol.
Menangani kesalahan
Spesifikasi Promises/A+ menuntut bahwa jika sebuah janji ditolak atau kesalahan dilemparkan ke dalam penangan penolakan, itu harus ditangani oleh penangan penolakan yang "hilir" dari sumbernya.
Memanfaatkan teknik trickle down di bawah ini memberikan cara yang bersih untuk menangani kesalahan:
signupPayingUser .then(displayHoorayMessage) .then(queueWelcomeEmail) .then(queueHandwrittenPostcard) .then(redirectToThankYouPage) .then(null, displayAndSendErrorReport)
Karena penangan penolakan hanya ditambahkan di bagian paling akhir rantai, jika ada penangan pemenuhan dalam rantai yang ditolak atau menimbulkan kesalahan, penangan itu akan menetes ke bawah hingga menabrak displayAndSendErrorReport
.
Mari kembali ke dadu kesayangan kita dan lihat itu beraksi. Misalkan kita hanya ingin melempar dadu secara asinkron dan mencetak hasilnya:
var tossTable = { 1: 'one', 2: 'two', 3: 'three', 4: 'four', 5: 'five', 6: 'six' }; function toss() { return new RSVP.Promise(function(fulfill, reject) { var n = Math.floor(Math.random() * 6) + 1; fulfill(n); }); } function logAndTossAgain(toss) { var tossWord = tossTable[toss]; console.log("Tossed a " + tossWord.toUppercase() + "."); } toss() .then(logAndTossAgain) .then(logAndTossAgain) .then(logAndTossAgain);
Ketika Anda menjalankan ini, tidak ada yang terjadi. Tidak ada yang dicetak di konsol dan sepertinya tidak ada kesalahan yang terjadi.
Pada kenyataannya, kesalahan memang terjadi, kami hanya tidak melihatnya karena tidak ada penangan penolakan dalam rantai. Karena kode di penangan dieksekusi secara tidak sinkron, dengan tumpukan baru, kode itu bahkan tidak keluar ke konsol. Mari kita perbaiki ini:
function logAndTossAgain(toss) { var tossWord = tossTable[toss]; console.log("Tossed a " + tossWord.toUpperCase() + "."); } function logErrorMessage(error) { console.log("Oops: " + error.message); } toss() .then(logAndTossAgain) .then(logAndTossAgain) .then(logAndTossAgain) .then(null, logErrorMessage);
Menjalankan kode di atas memang menunjukkan kesalahan sekarang:
"Tossed a TWO." "Oops: Cannot read property 'toUpperCase' of undefined"
Kami lupa mengembalikan sesuatu dari logAndTossAgain
dan janji kedua dipenuhi dengan undefined
. Handler pemenuhan berikutnya kemudian meledak mencoba memanggil toUpperCase
untuk itu. Itu hal penting lainnya untuk diingat: selalu kembalikan sesuatu dari penangan, atau bersiaplah di penangan berikutnya agar tidak ada yang lewat.
Bangunan lebih tinggi
Kita sekarang telah melihat dasar-dasar janji JavaScript dalam kode contoh tutorial ini. Manfaat besar menggunakan mereka adalah bahwa mereka dapat dikomposisikan dengan cara sederhana untuk menghasilkan janji "majemuk" dengan perilaku yang kita inginkan. Pustaka rsvp.js
menyediakan beberapa di antaranya, dan Anda selalu dapat membuatnya sendiri menggunakan primitif dan yang tingkat lebih tinggi ini.
Untuk contoh terakhir yang paling kompleks, kami melakukan perjalanan ke dunia permainan peran AD&D dan melempar dadu untuk mendapatkan skor karakter. Skor tersebut diperoleh dengan melempar tiga dadu untuk setiap keterampilan karakter.
Biarkan saya menempelkan kode di sini terlebih dahulu dan kemudian menjelaskan apa yang baru:
function toss() { var n = Math.floor(Math.random() * 6) + 1; return new RSVP.resolve(n); // [1] } function threeDice() { var tosses = []; function add(x, y) { return x + y; } for (var i=0; i<3; i++) { tosses.push(toss()); } return RSVP.all(tosses).then(function(results) { // [2] return results.reduce(add); // [3] }); } function logResults(result) { console.log("Rolled " + result + " with three dice."); } function logErrorMessage(error) { console.log("Oops: " + error.message); } threeDice() .then(logResults) .then(null, logErrorMessage);
Kita akrab dengan toss
dari contoh kode terakhir. Itu hanya menciptakan janji yang selalu dipenuhi dengan hasil melempar dadu. Saya menggunakan RSVP.resolve
, metode mudah yang membuat janji seperti itu dengan sedikit upacara (lihat [1] dalam kode di atas).
Dalam threeDice
, saya membuat 3 janji yang masing-masing mewakili lemparan dadu dan akhirnya menggabungkannya dengan RSVP.all
. RSVP.all
mengambil serangkaian janji dan diselesaikan dengan serangkaian nilai yang diselesaikan, satu untuk setiap janji konstituen, sambil mempertahankan pesanannya. Itu berarti kita memiliki hasil lemparan dalam results
(lihat [2] pada kode di atas), dan kami mengembalikan janji yang dipenuhi dengan jumlah mereka (lihat [3] pada kode di atas).
Menyelesaikan janji yang dihasilkan kemudian mencatat jumlah total:
"Rolled 11 with three dice"
Menggunakan janji untuk memecahkan masalah nyata
Janji JavaScript digunakan untuk memecahkan masalah dalam aplikasi yang jauh lebih kompleks daripada lemparan dadu tanpa alasan yang sinkron .
Jika Anda mengganti melempar tiga dadu dengan mengirimkan tiga permintaan ajax ke titik akhir yang terpisah dan melanjutkan ketika semuanya berhasil kembali (atau jika ada yang gagal), Anda sudah memiliki aplikasi yang berguna dari promise dan RSVP.all
.
Janji, bila digunakan dengan benar, menghasilkan kode yang mudah dibaca yang lebih mudah untuk dipikirkan, dan dengan demikian lebih mudah untuk di-debug daripada panggilan balik. Tidak perlu menyiapkan konvensi mengenai, misalnya, penanganan kesalahan karena sudah menjadi bagian dari spesifikasi.
Kami hampir tidak mengetahui apa yang bisa dilakukan janji dalam tutorial JavaScript ini. Pustaka Promise menyediakan selusin metode dan konstruktor tingkat rendah yang siap membantu Anda. Kuasai ini, dan langit adalah batas dalam apa yang dapat Anda lakukan dengan mereka.
Tentang Penulis
Balint Erdi adalah penggemar permainan peran dan AD&D yang hebat sejak lama, dan sekarang merupakan penggemar yang menjanjikan dan penggemar Ember.js. Apa yang konstan adalah hasratnya untuk rock & roll. Karena itulah ia memutuskan untuk menulis sebuah buku di Ember.js yang menggunakan rock & roll sebagai tema aplikasi dalam buku tersebut. Daftar di sini untuk mengetahui kapan diluncurkan.