Pemrograman asinkron yang elegan dengan janji-janji

Selamat siang teman!

Janji (janji) adalah fitur yang relatif baru dari JavaScript yang memungkinkan Anda untuk menunda pelaksanaan suatu tindakan sampai penyelesaian tindakan sebelumnya atau untuk menanggapi eksekusi tindakan yang gagal. Ini berkontribusi pada penentuan urutan operasi asinkron yang benar. Artikel ini membahas cara kerja janji, bagaimana janji itu digunakan di Web API, dan bagaimana Anda bisa menulis janji Anda sendiri.

Ketentuan: melek komputer dasar, pengetahuan dasar-dasar JS.
Tujuan: memahami apa janji itu dan bagaimana janji itu digunakan.

Apa itu janji?


Kami meninjau secara singkat janji - janji di artikel pertama kursus, di sini kami akan mempertimbangkannya secara lebih rinci.

Intinya, janji adalah objek yang mewakili kondisi perantara operasi - itu "menjanjikan" bahwa hasilnya akan dikembalikan di masa depan. Tidak diketahui pasti kapan operasi akan selesai dan kapan hasilnya akan dikembalikan, tetapi ada jaminan bahwa ketika operasi selesai, kode Anda akan melakukan sesuatu dengan hasil atau dengan anggun menangani kesalahan.

Sebagai aturan, berapa lama waktu yang diperlukan untuk operasi asinkron (tidak terlalu lama!) Tidak terlalu menarik bagi kita daripada kemungkinan tanggapan langsung terhadap penyelesaiannya. Dan, tentu saja, senang mengetahui bahwa sisa kode tidak diblokir.

Salah satu janji yang paling umum adalah API Web yang mengembalikan janji. Mari kita lihat aplikasi obrolan video hipotetis. Aplikasi memiliki jendela dengan daftar teman-teman pengguna, mengklik tombol di sebelah nama (atau avatar) pengguna memulai panggilan video.

Tombol pengendali panggilan getUserMedia ()untuk mengakses kamera dan mikrofon pengguna. Dari saat getUserMedia () menghubungi pengguna untuk izin (untuk menggunakan perangkat, perangkat mana yang digunakan jika pengguna memiliki beberapa mikrofon atau kamera, hanya panggilan suara, di antara hal-hal lain), getUserMedia () mengharapkan tidak hanya keputusan pengguna, tetapi juga melepaskan perangkat jika sedang digunakan. Selain itu, pengguna mungkin tidak segera merespons. Semua ini dapat menyebabkan keterlambatan waktu.

Jika permintaan izin untuk menggunakan perangkat dibuat dari utas utama, browser diblokir sampai getUserMedia () selesai. Itu tidak bisa diterima. Tanpa janji, semua yang ada di browser menjadi "tidak dapat diklik" sampai pengguna memberikan izin untuk menggunakan mikrofon dan kamera. Oleh karena itu, alih-alih menunggu keputusan pengguna dan mengembalikan MediaStream untuk aliran yang dibuat dari sumber (kamera dan mikrofon), getUserMedia () mengembalikan janji yang diproses MediaStream segera setelah tersedia.

Kode aplikasi obrolan video mungkin terlihat seperti ini:

function handle CallButton(evt){
    setStatusMessage('Calling...')
    navigator.mediaDevices.getUserMedia({video: true, audio: true})
    .then(chatStream => {
        selfViewElem.srcObject = chatStream
        chatStream.getTracks().forEach(track => myPeerConnection.addTrack(track, chatStream))
        setStatusMessage('Connected')
    }).catch(err => {
        setStatusMessage('Failed to connect')
    })
}

Fungsi dimulai dengan memanggil setStatusMessage (), menampilkan pesan 'Memanggil ...', yang berfungsi sebagai indikator bahwa upaya sedang dilakukan untuk melakukan panggilan. Kemudian getUserMedia () dipanggil, meminta aliran yang berisi trek video dan audio. Setelah aliran terbentuk, elemen video dipasang untuk menampilkan aliran dari kamera yang disebut 'tampilan sendiri', trek audio ditambahkan ke WebRTC RTCPeerConnection , yang merupakan koneksi ke pengguna lain. Setelah itu, status diperbarui ke 'Terhubung'.

Jika getUserMedia () gagal, blok tangkap diluncurkan. Ini menggunakan setStatusMessage () untuk menampilkan pesan kesalahan.

Perhatikan bahwa panggilan untuk mendapatkanUserMedia () dikembalikan bahkan jika aliran video belum diterima. Bahkan jika fungsi handleCallButton () mengembalikan kontrol ke kode yang memanggilnya, begitu getUserMedia () menyelesaikan eksekusi, ia akan memanggil penangan. Hingga aplikasi "memahami" bahwa siaran telah dimulai, getUserMedia () akan berada dalam mode siaga.

Catatan: Anda dapat mempelajari lebih lanjut tentang ini di artikel "Sinyal dan panggilan video" . Artikel ini memberikan kode yang lebih lengkap daripada yang kita gunakan dalam contoh.

Masalah Fungsi Panggilan Balik


Untuk memahami mengapa janji adalah solusi yang baik, akan berguna untuk beralih ke cara lama menulis panggilan balik untuk melihat apa masalah mereka.

Sebagai contoh, pertimbangkan memesan pizza. Pesanan pizza yang berhasil terdiri dari beberapa langkah yang harus dilakukan berurutan, satu demi satu:

  1. Pilih isian. Ini bisa memakan waktu lama jika Anda memikirkannya untuk waktu yang lama, dan gagal jika Anda berubah pikiran dan memesan kari.
  2. Kami memesan. Memasak pizza membutuhkan waktu dan mungkin gagal jika restoran kekurangan bahan-bahan yang diperlukan.
  3. Kami mendapatkan pizza dan makan. Menerima pizza mungkin gagal jika, misalnya, kami tidak dapat membayar pesanan.

Pseudocode gaya lama yang menggunakan fungsi callback mungkin terlihat seperti ini:

chooseToppings(function(toppings){
    placeOrder(toppings, function(order){
        collectOrder(order, function(pizza){
            eatPizza(pizza)
        }, failureCallback)
    }, failureCallback)
}, failureCallback)

Kode semacam itu sulit dibaca dan dipelihara (sering disebut "neraka panggilan balik" atau "neraka panggilan balik"). Fungsi failureCallback () harus dipanggil di setiap level penyarangan. Ada masalah lain.

Kami menggunakan janji


Janji-janji membuat kode lebih bersih, lebih mudah dipahami dan didukung. Jika kita menulis ulang kodesemu menggunakan janji asinkron, kita mendapatkan yang berikut:

chooseToppings()
.then(function(toppings){
    return placeOrder(toppings)
})
.then(function(order){
    return collectOrder(order)
})
.then(function(pizza){
    eatPizza(pizza)
})
.catch(failureCallback)

Ini jauh lebih baik - kita melihat apa yang terjadi, kita menggunakan satu blok .catch () untuk menangani semua kesalahan, fungsinya tidak memblokir aliran utama (sehingga kita bisa bermain video game sambil menunggu pizza), setiap operasi dijamin akan dilakukan setelah yang sebelumnya selesai. Karena setiap janji mengembalikan janji, kita dapat menggunakan rantai .then. Bagus kan?

Menggunakan fungsi panah, pseudo-code dapat lebih disederhanakan:

chooseToppings()
.then(toppings =>
    placeOrder(toppings)
)
.then(order =>
    collectOrder(order)
)
.then(pizza =>
    eatPizza(pizza)     
)
.catch(failureCallback)

Atau bahkan seperti ini:

chooseToppings()
.then(toppings => placeOrder(toppings))
.then(order => collectOrder(order))
.then(pizza => eatPizza(pizza))
.catch(failureCallback)

Ini berfungsi karena () => x identik dengan () => {return x}.

Anda bahkan dapat melakukan ini (karena fungsi hanya melewati parameter, kami tidak perlu layering):

chooseToppings().then(placeOrder).then(collectOrder).then(eatPizza).catch(failureCallback)

Kode tersebut lebih sulit dibaca dan tidak dapat digunakan dengan konstruksi yang lebih kompleks daripada di pseudocode.

Catatan: pseudo-code masih dapat ditingkatkan menggunakan async / menunggu, yang akan dibahas dalam artikel mendatang.

Pada intinya, janji serupa dengan pendengar acara, tetapi dengan beberapa perbedaan:

  • Sebuah janji hanya terpenuhi satu kali (sukses atau gagal). Itu tidak dapat dieksekusi dua kali atau beralih dari sukses ke kegagalan atau sebaliknya setelah operasi selesai.
  • Jika janji itu selesai, dan kami menambahkan fungsi callback untuk memproses hasilnya, maka fungsi ini akan dipanggil, terlepas dari kenyataan bahwa peristiwa terjadi sebelum fungsi ditambahkan.

Sintaks janji dasar: contoh nyata


Mengetahui janji itu penting karena banyak API Web menggunakannya dalam fungsi yang berpotensi melakukan tugas kompleks. Bekerja dengan teknologi web modern melibatkan penggunaan janji. Nanti kita akan belajar bagaimana menulis janji kita sendiri, tetapi untuk sekarang, mari kita lihat beberapa contoh sederhana yang dapat ditemukan di Web API.

Pada contoh pertama, kami menggunakan metode fetch () untuk mendapatkan gambar dari jaringan, metode blob () untuk mengonversi konten tubuh respons ke objek Blob dan menampilkan objek ini di dalam elemen <img> . Contoh ini sangat mirip dengan contoh dari artikel pertama , tetapi kami akan melakukannya sedikit berbeda.

Catatan: contoh berikut tidak akan berfungsi jika Anda menjalankannya dari file (mis. Menggunakan file: // URL). Anda perlu menjalankannya melalui server lokal atau menggunakan solusi online seperti halaman Glitch atau GitHub .

1. Pertama-tama, unggah HTML dan gambar yang akan kami terima.

2. Tambahkan elemen <script> ke akhir <body> .

3. Di dalam elemen <script>, tambahkan baris berikut:

let promise = fetch('coffee.jpg')

Metode fetch (), yang menggunakan URL gambar sebagai parameter, digunakan untuk mendapatkan gambar dari jaringan. Sebagai parameter kedua, kita dapat menentukan objek dengan pengaturan, tetapi untuk saat ini kita akan membatasi diri pada opsi sederhana. Kami menyimpan janji yang dikembalikan oleh fetch () dalam variabel janji. Seperti disebutkan sebelumnya, janji adalah objek yang mewakili negara perantara - nama resmi untuk negara tertentu sedang menunggu.

4. Untuk bekerja dengan hasil penyelesaian janji yang berhasil (dalam kasus kami, ketika Respons kembali ), kami memanggil metode .then (). Fungsi panggilan balik di dalam blok .then () (sering disebut sebagai pelaksana) diluncurkan hanya ketika janji berhasil diselesaikan dan objek Respons dikembalikan - mereka mengatakan bahwa janji telah selesai (terpenuhi, terpenuhi). Respons diberikan sebagai parameter.

Catatan. Pengoperasian blok .then () mirip dengan operasi pendengar peristiwa AddEventListener (). Itu dipicu hanya setelah peristiwa itu terjadi (setelah memenuhi janji). Perbedaan antara keduanya adalah bahwa. Maka () hanya dapat dipanggil sekali, sedangkan pendengar dirancang untuk beberapa panggilan.

Setelah menerima respons, kami memanggil metode blob () untuk mengubah respons menjadi objek Blob. Ini terlihat seperti ini:

response => response.blob()

... yang merupakan entri pendek untuk:

function (response){
    return response.blob()
}

OK, kata-kata yang cukup. Tambahkan baris berikut setelah baris pertama:

let promise2 = promise.then(response => response.blob())

5. Setiap panggilan ke .then () menciptakan janji baru. Ini sangat berguna: karena blob () juga mengembalikan sebuah janji, kita dapat memproses objek Blob dengan memanggil .then () pada janji kedua. Mengingat bahwa kita ingin melakukan sesuatu yang lebih rumit daripada memanggil metode dan mengembalikan hasilnya, kita perlu membungkus tubuh fungsi dalam kurung kurawal (jika tidak, pengecualian akan dilemparkan):

Tambahkan berikut ini di akhir kode:

let promise3 = promise2.then(myBlob => {

})

6. Mari kita isi tubuh fungsi pelaksana. Tambahkan baris berikut ke kawat gigi:

let objectURL = URL.createObjectURL(myBlob)
let image = document.createElement('img')
image.src = objectURL
document.body.appendChild(image)

Di sini kita memanggil metode URL.createObjectURL (), meneruskannya sebagai parameter Blob, yang mengembalikan janji kedua. Kami mendapatkan tautan ke objek. Kemudian kita membuat elemen <img>, mengatur atribut src dengan nilai tautan objek dan menambahkannya ke DOM sehingga gambar muncul di halaman.

Jika Anda menyimpan HTML dan memuatnya di browser, Anda akan melihat bahwa gambar ditampilkan seperti yang diharapkan. Kerja bagus!

Catatan. Anda mungkin memperhatikan bahwa contoh-contoh ini agak dibuat-buat. Anda dapat melakukannya tanpa metode fetch () dan blob () dan menetapkan URL <img> yang sesuai, coffee.jpg. Kami menggunakan cara ini untuk menunjukkan bahwa bekerja dengan janji dengan contoh sederhana.

Respon Kegagalan


Kami lupa sesuatu - kami tidak memiliki penangan kesalahan jika salah satu janji gagal (akan ditolak). Kami dapat menambahkan penangan kesalahan menggunakan metode .catch (). Ayo lakukan itu:

let errorCase = promise3.catch(e => {
    console.log('There has been a problem with your fetch operation: ' + e.message)
})

Untuk melihat ini dalam tindakan, tentukan URL gambar yang salah dan muat ulang halaman. Pesan kesalahan akan muncul di konsol.

Dalam hal ini, jika kita tidak menambahkan blok .catch (), pesan kesalahan juga akan ditampilkan di konsol. Namun .catch () memungkinkan kita untuk menangani kesalahan seperti yang kita inginkan. Dalam aplikasi nyata, blok .catch () Anda mungkin mencoba untuk mengambil kembali gambar atau menampilkan gambar default, atau meminta pengguna untuk memilih gambar lain atau melakukan sesuatu yang lain.

Catatan. Tonton demo langsung ( kode sumber ).

Blok penggabungan


Faktanya, pendekatan yang kita gunakan untuk menulis kode tidak optimal. Kami sengaja menggunakan cara ini sehingga Anda dapat memahami apa yang terjadi di setiap tahap. Seperti yang ditunjukkan sebelumnya, kita dapat menggabungkan blok .then () (dan .catch ()). Kode kami dapat ditulis ulang sebagai berikut (lihat juga simple-fetch-chained.html di GitHub):

fetch('coffee.jpg')
.then(response => response.blob())
.then(myBlob => {
    let objectURL = URL.createObjectURL(myBlob)
    let image = document.createElement('img')
    image.src = objectURL
    document.body.appendChild(image)
})
.catch(e => {
    console.log('There has been a problem with your fetch operation: ' + e.message)
})

Ingat bahwa nilai yang dikembalikan oleh janji dilewatkan sebagai parameter ke pelaksana fungsi selanjutnya dari blok .then ().

Catatan. Blok .then () /. Catch () dalam janji-janji adalah ekuivalen asinkron dari blok try ... catch. Ingat: coba sinkron ... catch tidak akan berfungsi dalam kode asinkron.

Kesimpulan Terminologi Janji


Mari kita bekal dan menulis panduan singkat yang dapat Anda gunakan di masa depan. Untuk menggabungkan pengetahuan, kami menyarankan Anda membaca bagian sebelumnya beberapa kali.

1. Ketika janji itu dibuat, mereka mengatakan bahwa itu dalam keadaan harapan.
2. Ketika janji itu dikembalikan, mereka mengatakan bahwa itu telah selesai (diselesaikan):

  1. 1. Janji yang berhasil diselesaikan disebut terpenuhi. Ini mengembalikan nilai yang bisa diperoleh melalui rantai dari. Kemudian () di akhir janji. Fungsi eksekusi di blok .then () berisi nilai yang dikembalikan oleh janji.
  2. 2. Janji yang gagal diselesaikan disebut ditolak. Ini mengembalikan alasan, pesan kesalahan yang mengarah pada penolakan janji. Alasan ini dapat diperoleh melalui blok .catch () di akhir janji.

Jalankan kode setelah beberapa janji


Kami belajar dasar-dasar menggunakan janji. Sekarang mari kita lihat fitur yang lebih canggih. Rantai dari .then () baik-baik saja, tetapi bagaimana jika kita ingin memanggil beberapa blok janji satu per satu?

Anda dapat melakukan ini menggunakan metode standar Promise.all (). Metode ini mengambil array janji sebagai parameter dan mengembalikan janji baru ketika semua janji dalam array dipenuhi. Itu terlihat seperti ini:

Promise.all([a,b,c]).then(values => {
        ...
})

Jika semua janji dipenuhi, pelaksana array blok .then () akan diteruskan sebagai parameter. Jika setidaknya satu dari janji tidak terpenuhi, seluruh blok akan ditolak.

Ini bisa sangat membantu. Bayangkan kami menerima informasi karena secara dinamis mengisi antarmuka pengguna dengan konten di halaman kami. Dalam banyak kasus, lebih masuk akal untuk menerima semua informasi dan menampilkannya pada halaman lebih lambat daripada menampilkan informasi dalam beberapa bagian.

Mari kita lihat contoh lain:
1. Unduh templat halaman dan letakkan tag <script> sebelum tag </body> penutup.

2. Unduh file sumber ( coffee.jpg , tea.jpg dan description.txt) atau ganti dengan milik Anda.

3. Dalam skrip, pertama-tama kita mendefinisikan fungsi yang mengembalikan janji yang kita berikan ke Promise.all (). Ini akan mudah dilakukan jika kita menjalankan Promise.all () setelah menyelesaikan tiga operasi pengambilan (). Kita dapat melakukan hal berikut:

let a = fetch(url1)
let b = fetch(url2)
let c = fetch(url3)

Promise.all([a, b, c]).then(values => {
    ...
})

Ketika janji terpenuhi, variabel "nilai" akan berisi tiga objek Respons, satu dari setiap operasi pengambilan () yang diselesaikan.

Namun, kami tidak menginginkan ini. Tidak masalah bagi kami saat operasi fetch () selesai. Yang kami inginkan adalah data yang dimuat. Ini berarti bahwa kami ingin menjalankan blok Promise.all () setelah menerima gumpalan valid yang mewakili gambar dan string teks yang valid. Kita dapat menulis fungsi yang melakukan ini; tambahkan berikut ini ke elemen <script> Anda:

function fetchAndDecode(url, type){
    return fetch(url).then(response => {
        if(type === 'blob'){
            return response.blob()
        } else if(type === 'text'){
            return response.text()
        }
    }).catch(e => {
        console.log('There has been a problem with your fetch operation ' + e.message)
    })
}

Itu terlihat agak rumit, jadi mari kita pergi melalui kode langkah demi langkah:

1. Pertama-tama, kita mendeklarasikan suatu fungsi dan meneruskannya URL dan jenis file yang dihasilkan.

2. Struktur fungsi ini mirip dengan yang kita lihat pada contoh pertama - kita memanggil fungsi fetch () untuk mendapatkan file di URL tertentu dan kemudian mentransfer file ke janji lain yang mengembalikan tubuh respons (baca) yang diterjemahkan. Pada contoh sebelumnya, selalu metode blob ().

3. Ada dua perbedaan:

  • Pertama, janji pengembalian kedua tergantung pada jenis nilai. Di dalam fungsi eksekusi, kita menggunakan pernyataan if ... else jika untuk mengembalikan janji tergantung pada jenis file yang perlu kita dekode (dalam hal ini, kita memilih antara gumpalan dan teks, tetapi contohnya dapat dengan mudah diperluas untuk bekerja dengan jenis lain).
  • -, «return» fetch(). , (.. , blob() text(), , , ). , return .

4. Pada akhirnya, kita memanggil metode .catch () untuk menangani kesalahan yang mungkin muncul dalam janji dalam array yang diteruskan ke .all (). Jika salah satu janji ditolak, blok tangkap akan memberi tahu kami dengan janji apa masalahnya. Blok .all () masih akan dieksekusi, tetapi itu tidak akan menampilkan hasil yang ada kesalahannya. Jika Anda ingin .all () ditolak, tambahkan blok .catch () di akhir.

Kode di dalam fungsi asinkron dan didasarkan pada janji, sehingga seluruh fungsi berfungsi seperti janji.

4. Selanjutnya, kita memanggil fungsi kita tiga kali untuk memulai proses menerima dan mendekode gambar dan teks, dan menempatkan masing-masing janji dalam variabel. Tambahkan yang berikut ini:

let coffee = fetchAndDecode('coffee.jpg', 'blob')
let tea = fetchAndDecode('tea.jpg', 'blob')
let description = fetchAndDecode('description.txt', 'text')

5. Selanjutnya, kami mendeklarasikan blok Promise.all () untuk menjalankan beberapa kode hanya setelah ketiga janji telah dipenuhi. Tambahkan blok dengan fungsi eksekutor kosong di dalam .then ():

Promise.all([coffee, tea, description]).then(values => {

})

Anda dapat melihat bahwa dibutuhkan sejumlah janji sebagai parameter. Kontraktor akan mulai hanya setelah ketiga janji dipenuhi; ketika ini terjadi, hasil dari setiap janji (badan respons yang diterjemahkan) akan ditempatkan dalam array, ini dapat direpresentasikan sebagai [hasil kopi, hasil teh, hasil deskripsi].

6. Terakhir, tambahkan berikut ini ke pelaksana (di sini kami menggunakan kode sinkron yang cukup sederhana untuk meletakkan hasilnya dalam variabel (membuat objek URL dari gumpalan) dan menampilkan gambar dan teks pada halaman):

console.log(values)
//       
let objectURL1 = URL.createObjectURL(values[0])
let objectURL2 = URL.createObjectURL(values[1])
let descText = values[2]

//  
let image1 = document.createElement('img')
let image2 = document.createElement('img')
image1.src = objectURL1
image2.src = objectURL2
document.body.appendChild(image1)
document.body.appendChild(image2)

//  
let para = document.createElement('p')
para.textContent = descText
document.body.appendChild(para)

Simpan perubahan dan muat ulang halaman. Anda harus melihat bahwa semua komponen antarmuka pengguna memuat, meskipun ini tidak terlihat sangat menarik.

Catatan. Jika Anda mengalami kesulitan, Anda dapat membandingkan versi kode dengan versi kami - ini adalah demo langsung dan kode sumber .

Catatan. Untuk meningkatkan kode ini, Anda dapat mengulang elemen yang ditampilkan, menerima dan mendekodekan masing-masing, dan kemudian mengulang melalui hasil di dalam Promise.all (), meluncurkan fungsi yang berbeda untuk menampilkan setiap hasil tergantung pada jenisnya. Ini akan memungkinkan Anda untuk bekerja dengan sejumlah elemen.

Selain itu, Anda dapat menentukan jenis file yang dihasilkan tanpa harus secara eksplisit menentukan tipe properti. Ini, misalnya, dapat dilakukan denganresponse.headers.get ('tipe konten') untuk memeriksa header Tipe Konten Protokol HTTP.

Jalankan kode setelah memenuhi / menolak janji


Seringkali, Anda mungkin perlu mengeksekusi kode setelah janji selesai, terlepas dari apakah itu dieksekusi atau ditolak. Sebelumnya, kami harus memasukkan kode yang sama di blok .then () dan .catch (), misalnya:

myPromise
.then(response => {
    doSomething(response)
    runFinalCode()
})
.catch(e => {
    returnError(e)
    runFinalCode()
})

Di browser modern, metode .finally () tersedia, yang memungkinkan Anda menjalankan kode setelah janji selesai, yang menghindari pengulangan dan membuat kode lebih elegan. Kode dari contoh sebelumnya dapat ditulis ulang sebagai berikut:

myPromise
.then(response => {
    doSomething(response)
})
.catch(e => {
    returnError(e)
})
.finally(() => {
    runFinalCode()
})

Anda dapat melihat penggunaan pendekatan ini dengan contoh nyata - demo langsung janji-akhirnya.html ( kode sumber ). Ini bekerja sama dengan Promise.all () dari contoh sebelumnya, kecuali bahwa kita akhirnya menambahkan () ke akhir rantai di fungsi fetchAndDecode ():

function fetchAndDecode(url, type){
    return fetch(url).then(response => {
        if(type === 'blob'){
            return response.blob()
        } else if(type === 'text'){
            return response.text()
        }
    }).catch(e => {
        console.log(`There has been a problem with your fetch operation for resource "${url}": ${e.message}`)
    }).finally(() => {
        console.log(`fetch attempt for "${url}" finished.`)
    })
}

Kami akan menerima pesan tentang selesainya setiap upaya untuk mendapatkan file.

Catatan. maka () / catch () / akhirnya () adalah ekuivalen asinkron dari try sinkron () / catch () / akhirnya ().

Menulis Janji Anda Sendiri


Berita baiknya adalah Anda sudah tahu cara melakukannya. Saat Anda menggabungkan beberapa janji dengan .then (), atau menggabungkannya untuk menyediakan fungsionalitas tertentu, Anda membuat fungsi asinkron dan janji berbasis Anda sendiri. Ambil contoh fungsi fetchAndDecode () kami dari contoh sebelumnya.

Menggabungkan berbagai API berbasis janji untuk membuat fungsionalitas spesifik adalah cara paling umum untuk bekerja dengan janji, yang menunjukkan fleksibilitas dan kekuatan API modern. Namun, ada cara lain.

Janji Pembuat ()


Anda dapat membuat janji sendiri menggunakan konstruktor Promise (). Ini mungkin diperlukan dalam situasi di mana Anda memiliki kode dari API asinkron yang lama yang perlu Anda "besarkan". Hal ini dilakukan agar dapat bekerja secara simultan baik dengan kode lama, perpustakaan, atau "kerangka kerja" yang ada, dan dengan kode berbasis janji yang baru.

Mari kita lihat contoh sederhana - di sini kita bungkus panggilan setTimeout () dengan janji - ini akan memulai fungsinya dalam 2 detik, yang akan memenuhi janji (menggunakan panggilan resol () yang diteruskan) dengan string "Sukses!".

let timeoutPromise = new Promise((resolve, reject) => {
    setTimeout(function(){
        resolve('Success!')
    }, 2000)
})

resolving () dan reject () adalah fungsi yang dipanggil untuk memenuhi atau menolak janji baru. Dalam hal ini, janji dipenuhi dengan string "Sukses!".

Saat Anda menyebut janji ini, Anda bisa menambahkan blok .then () untuknya bekerja lebih jauh dengan Garis "Sukses!". Jadi kami dapat menampilkan baris dalam pesan:

timeoutPromise
.then((message) => {
    alert(message)
})

... atau lebih:

timeoutPromise.then(alert)

Tonton demo langsung ( kode sumber ).

Contoh yang diberikan tidak terlalu fleksibel - janji hanya dapat dipenuhi dengan garis sederhana, kami tidak memiliki penangan kesalahan - tolak () (sebenarnya setTimeout () tidak memerlukan penangan kesalahan, jadi dalam hal ini tidak masalah).

Catatan. Mengapa menyelesaikan () daripada memenuhi ()? Saat ini, jawabannya adalah ini: sulit untuk dijelaskan.

Kami bekerja dengan penolakan terhadap sebuah janji


Kita dapat membuat janji yang ditolak menggunakan metode tolak () - seperti halnya resol (), tolak () mengambil nilai sederhana, tetapi tidak seperti tekad (), nilai sederhana bukanlah hasilnya, tetapi alasan penolakan, yaitu. kesalahan yang diteruskan ke blok .catch ().

Mari kita kembangkan contoh sebelumnya dengan menambahkan syarat untuk menolak janji, serta kemampuan untuk menerima pesan selain pesan sukses.

Ambil contoh sebelumnya dan tulis ulang seperti ini:

function timeoutPromise(message, interval){
    return new Promise((resolve, reject) => {
        if(message === '' || typeof message !== 'string'){
            reject('Message is empty or not a string')
        } else if(interval < 0 || typeof interval !== number){
            reject('Interval is negative or not a number')
        } else{
            setTimeout(function(){
                resolve(message)
            }, interval)
        }
    })
}

Di sini kita menyampaikan dua argumen ke fungsi - pesan dan interval (waktu tunda). Dalam suatu fungsi, kami mengembalikan objek Janji.

Dalam konstruktor Janji, kami melakukan beberapa pemeriksaan menggunakan struktur if ... else:

  1. Pertama, kami memeriksa pesannya. Jika kosong atau bukan string, maka kami menolak janji itu dan melaporkan kesalahan.
  2. Kedua, kami memeriksa penundaan waktu. Jika itu negatif atau bukan angka, maka kami juga menolak janji itu dan melaporkan kesalahan.
  3. Akhirnya, jika kedua argumen OK, tampilkan pesan setelah waktu tertentu (interval) menggunakan setTimeout ().

Karena timeoutPromise () mengembalikan sebuah janji, kita dapat menambahkan .then (), .catch (), dll. Untuk meningkatkan fungsionalitasnya. Ayo lakukan itu:

timeoutPromise('Hello there!', 1000)
.then(message => {
    alert(message)
}).catch(e => {
    console.log('Error: ' + e)
})

Setelah menyimpan perubahan dan menjalankan kode, Anda akan melihat pesan setelah satu detik. Sekarang coba lewati string kosong sebagai pesan atau angka negatif sebagai interval. Anda akan melihat pesan kesalahan sebagai akibat dari menolak janji.

Catatan. Anda dapat menemukan versi kami dari contoh ini di GitHub - custom-janji2.html ( sumber ).

Contoh yang lebih realistis.


Contoh yang kami periksa membuatnya mudah untuk memahami konsep, tetapi tidak benar-benar tidak sinkron. Asynchrony dalam contoh disimulasikan menggunakan setTimeout (), meskipun masih menunjukkan kegunaan janji untuk membuat fungsi dengan alur kerja yang masuk akal, penanganan kesalahan yang baik, dll.

Salah satu contoh aplikasi asinkron yang berguna yang menggunakan konstruktor Promise () adalah idb library (IndexedDB with usability)Jake Archibald. Pustaka ini memungkinkan Anda untuk menggunakan API IndexedDB (berdasarkan fungsi panggilan balik API untuk menyimpan dan mengambil data di sisi klien), yang ditulis dalam gaya lama, dengan janji. Jika Anda melihat file perpustakaan utama, Anda akan melihat bahwa itu menggunakan teknik yang sama yang kami periksa di atas. Kode berikut mengonversi model kueri dasar yang digunakan oleh banyak metode IndexedDB ke model yang kompatibel dengan janji:

function promisifyRequest(request){
    return new Promise(function(resolve, reject){
        request.onsuccess = function(){
            resolve(request.result)
        }

        request.onerror = function(){
            reject(request.error)
        }
    })
}

Ini menambahkan beberapa penangan acara yang memenuhi atau menolak janji, yang sesuai:

  • Ketika permintaan berhasil , pawang onsuc memenuhi janji dengan hasil dari permintaan.
  • Ketika permintaan gagal , penangan onerror menolak janji dengan kesalahan permintaan.

Kesimpulan


Janji adalah cara terbaik untuk membuat aplikasi asinkron ketika kita tidak tahu nilai apa yang akan dihasilkan fungsi atau berapa lama waktu yang dibutuhkan. Mereka memungkinkan Anda untuk bekerja dengan urutan operasi asinkron tanpa menggunakan fungsi panggilan balik yang sangat bersarang, dan mereka mendukung gaya penanganan kesalahan dari pernyataan coba-coba catch ... catch.

Janji didukung oleh semua browser modern. Satu-satunya pengecualian adalah Opera Mini dan IE11 dan versi sebelumnya.

Dalam artikel ini, kami telah mempertimbangkan jauh dari semua fitur janji, hanya yang paling menarik dan berguna. Anda akan mengenal fitur dan teknik lain jika Anda ingin mempelajari lebih lanjut tentang janji.

Sebagian besar API Web modern didasarkan pada janji, sehingga janji harus diketahui. Di antara API Web ini kita dapat memberi nama WebRTC , API Audio Web , Media Capture dan Stream , dll. Janji-janji akan menjadi semakin populer, sehingga studi dan pemahaman mereka merupakan langkah penting menuju penguasaan JavaScript modern.

Juga lihat “Kesalahan Umum dalam JavaScript Menjanjikan Semua Orang Harus Tahu Tentang .

Terima kasih atas perhatian Anda.

Kritik konstruktif dipersilahkan.

All Articles