Visualisasi Janji dan Async / Menunggu



Selamat siang teman!

Saya hadir untuk Anda terjemahan artikel "JavaScript Visualisasi: Janji & Async / Menunggu" oleh Lydia Hallie.

Sudahkah Anda menemukan kode JavaScript yang ... tidak berfungsi seperti yang diharapkan? Ketika fungsi dilakukan dalam urutan yang sewenang-wenang, tidak dapat diprediksi, atau dilakukan dengan penundaan. Salah satu tugas utama dari janji adalah merampingkan pelaksanaan fungsi.

Keingintahuan saya yang tak pernah puas dan malam tanpa tidur terbayar lunas - terima kasih kepada mereka saya membuat beberapa animasi. Sudah waktunya untuk berbicara tentang janji: bagaimana mereka bekerja, mengapa mereka harus digunakan, dan bagaimana itu dilakukan.

pengantar


Saat menulis kode JS, kita sering harus berurusan dengan tugas yang bergantung pada tugas lain. Misalkan kita ingin mendapatkan gambar, kompres, terapkan filter dan simpan.

Pertama-tama, kita perlu mendapatkan gambar. Untuk melakukan ini, gunakan fungsinya getImage. Setelah memuat gambar, kami meneruskannya ke fungsi resizeImage. Setelah mengompresi gambar, kami menerapkan filter menggunakan fungsi applyFilter. Setelah mengompresi dan menerapkan filter, kami menyimpan gambar dan memberi tahu pengguna tentang keberhasilannya.

Sebagai hasilnya, kami memperoleh yang berikut:



Hmm ... Apakah Anda memperhatikan sesuatu? Terlepas dari kenyataan bahwa semuanya berfungsi, itu tidak terlihat cara terbaik. Kami mendapatkan banyak fungsi panggilan balik bersarang yang bergantung pada panggilan balik sebelumnya. Ini disebut panggilan balik neraka dan itu membuatnya sangat sulit untuk membaca dan memelihara kode.

Untungnya, hari ini kita punya janji.



Sintaksis Janji


Janji diperkenalkan di ES6. Dalam banyak manual Anda dapat membaca yang berikut:

Janji (janji) adalah nilai yang dipenuhi atau ditolak di masa depan.

Ya ... penjelasannya begitu-begitu. Pada suatu waktu, itu membuat saya menganggap janji sebagai sesuatu yang aneh, tidak jelas, semacam sihir. Apa mereka sebenarnya

Kita bisa membuat janji dengan konstruktor Promiseyang menggunakan fungsi callback sebagai argumen. Keren, mari kita coba:



Tunggu, apa yang akan kembali ke sini?

PromiseMerupakan objek yang berisi status ( [[PromiseStatus]]) dan nilai ( [[PromiseValue]]). Dalam contoh di atas, nilainya [[PromiseStatus]]adalah pending, dan nilai janji itu adalah undefined.

Jangan khawatir, Anda tidak harus berinteraksi dengan objek ini, Anda bahkan tidak dapat mengakses [[PromiseStatus]]dan properti [[PromiseValue]]. Namun, properti ini sangat penting ketika bekerja dengan janji.

PromiseStatus atau status janji dapat mengambil salah satu dari tiga nilai:

  • fulfilled: resolved (). ,
  • rejected: rejected (). -
  • pending: , pending (, )

Kedengarannya bagus, tetapi kapan janji mendapatkan status yang ditunjukkan? Dan mengapa status penting?

Dalam contoh di atas, kami meneruskan Promisefungsi panggilan balik sederhana ke konstruktor () => {}. Fungsi ini sebenarnya membutuhkan dua argumen. Nilai argumen pertama, biasanya disebut resolveatau res, adalah metode yang dipanggil ketika janji dieksekusi. Nilai argumen kedua, biasanya disebut rejectatau rej, adalah metode yang disebut ketika janji ditolak ketika ada sesuatu yang salah.



Mari kita lihat apa output ke konsol saat memanggil metode resolvedan reject:



Keren! Sekarang kita tahu bagaimana cara menghilangkan status pendingdan makna undefined. Status janji saat memanggil metode resolveadalah fulfilled, ketika reject-rejected.

[[PromiseValue]]atau nilai janji adalah nilai yang kita berikan pada metode resolveatau rejectsebagai argumen.

Fakta menyenangkan: Jake Archibald setelah membaca artikel ini menunjuk ke bug di Chrome, yang malah fulfilleddikembalikan resolved.



Oke, sekarang kita tahu cara bekerja dengan objek Promise. Tapi untuk apa ini digunakan?

Dalam pendahuluan, saya memberikan contoh di mana kita mendapatkan gambar, kompres, terapkan filter dan simpan. Lalu semuanya berakhir dengan neraka panggilan balik.

Untungnya, janji membantu mengatasi hal ini. Kami menulis ulang kode sehingga setiap fungsi mengembalikan janji.

Jika gambar telah dimuat, kami melakukan janji. Jika tidak, jika terjadi kesalahan, tolak janji:



Mari kita lihat apa yang terjadi ketika kode ini dijalankan di terminal:



Keren! Promis kembali dengan data parsing ("parsed"), seperti yang kami harapkan.

Tapi ... selanjutnya apa? Kami tidak tertarik pada subjek promis, kami tertarik pada datanya. Ada 3 metode bawaan untuk mendapatkan nilai Janji:

  • .then(): dipanggil setelah melakukan janji
  • .catch(): dipanggil setelah penolakan janji
  • .finally(): selalu dipanggil, baik setelah eksekusi maupun setelah penolakan janji



Metode ini .thenmengambil nilai yang diteruskan ke metode resolve:



Metode ini .catchmengambil nilai yang diteruskan ke metode reject:



Akhirnya, kami mendapatkan nilai yang diinginkan. Kita bisa melakukan apa saja dengan nilai ini.

Ketika kami yakin dalam pemenuhan atau penolakan janji, Anda dapat menulis Promise.resolvebaik Promise.rejectdengan nilai yang sesuai.



Ini adalah sintaks yang akan digunakan dalam contoh berikut.



Hasilnya .thenadalah nilai janji (yaitu, metode ini juga mengembalikan janji). Ini berarti bahwa kita dapat menggunakan sebanyak yang .thendiperlukan: hasil dari yang sebelumnya .thendilewatkan sebagai argumen ke yang berikutnya .then.



Dalam getImagekita dapat menggunakan beberapa .thenuntuk mentransfer gambar yang diproses ke fungsi selanjutnya.



Sintaks ini terlihat jauh lebih baik daripada tangga fungsi panggilan balik bersarang.



Microtasks dan tugas (makro)


Nah, sekarang kita tahu cara membuat janji dan cara mengekstraksi nilai dari mereka. Tambahkan beberapa kode ke skrip kami dan jalankan lagi:



Pertama, itu ditampilkan di konsol Start!. Ini normal, karena kita memiliki baris kode pertama console.log('Start!'). Nilai kedua yang ditampilkan pada konsol adalah End!, bukan nilai dari janji yang sudah selesai. Nilai janji ditampilkan terakhir. Kenapa ini terjadi?

Di sini kita melihat kekuatan janji. Meskipun JS adalah single-threaded, kita dapat membuat kode asinkron dengannya Promise.

Di mana lagi kita bisa mengamati perilaku asinkron? Beberapa metode yang dibangun dalam browser, seperti setTimeout, dapat mensimulasikan sinkronisasi.

Baik Dalam loop peristiwa (Event Loop), ada dua jenis antrian: antrian tugas (makro) atau hanya tugas ((makro) tugas antrian, tugas antrian) dan antrian mikrotasks atau hanya mikrotasks (antrian mikrotask, mikrotask).

Apa yang berlaku untuk masing-masing? Singkatnya, lalu:

  • Tugas Makro: setTimeout, setInterval, setImmediate
  • Microtasks: process.nextTick, Promise callback, queueMicrotask

Kita lihat Promisedi daftar microtasks. Ketika Promisemetode dieksekusi dan dipanggil then(), catch()atau finally(), fungsi panggilan balik dengan metode ditambahkan ke antrian mikrotask. Ini berarti bahwa panggilan balik dengan metode ini tidak dieksekusi segera, yang membuat kode JS asinkron.

Kapan metodenya then(), catch()atau finally()dijalankan? Tugas di loop acara memiliki prioritas berikut:

  1. Pertama, fungsi-fungsi dalam tumpukan panggilan dieksekusi. Nilai yang dikembalikan oleh fungsi-fungsi ini dihapus dari tumpukan.
  2. Setelah tumpukan dibebaskan, microtasks ditempatkan dan dieksekusi satu demi satu (microtasks dapat mengembalikan microtasks lainnya, menciptakan siklus microtasks tanpa akhir).
  3. Setelah tumpukan dan antrian mikrotask dilepaskan, loop acara memeriksa makro. Tugas makro didorong ke stack, dieksekusi, dan dihapus.



Pertimbangkan sebuah contoh:

  • Task1: Fungsi yang ditambahkan ke tumpukan segera, misalnya, dengan kode panggilan.
  • Task2, Task3, Task4: Mikrozadachi contoh thenPROMIS atau tugas ditambahkan melalui queueMicrotask.
  • Task5, Task6: tugas makro, misalnya, setTimeoutatausetImmediate



Pertama Task1mengembalikan nilai dan dihapus dari tumpukan. Kemudian mesin memeriksa mikrotasks dalam antrian yang sesuai. Setelah menambahkan dan kemudian menghapus mikrotasks dari tumpukan, mesin memeriksa tugas-tugas makro, yang juga ditambahkan ke tumpukan dan dihapus dari itu setelah mengembalikan nilai.

Kata-kata yang cukup. Mari kita menulis kodenya.



Dalam kode ini, kami memiliki setTimeouttugas makro dan tugas mikro .then. Jalankan kode dan lihat apa yang ditampilkan di konsol.

Catatan: dalam contoh di atas, saya menggunakan metode seperti console.log, setTimeoutdan Promise.resolve. Semua metode ini bersifat internal, sehingga tidak muncul di tumpukan jejak - jangan kaget ketika Anda tidak menemukannya di alat pemecahan masalah browser.

Di baris pertama yang kita milikiconsole.log. Itu ditambahkan ke tumpukan dan ditampilkan di konsol Start!. Setelah itu, metode ini dihapus dari tumpukan dan mesin terus mengurai kode.



Engine mencapai setTimeout, yang ditambahkan ke tumpukan. Metode ini adalah metode peramban bawaan: fungsi panggil baliknya ( () => console.log('In timeout')) ditambahkan ke Web API dan sudah ada sebelum timer menyala. Terlepas dari kenyataan bahwa penghitung waktu adalah 0, panggilan balik masih ditempatkan pertama kali di WebAPI dan kemudian dalam antrian tugas makro: setTimeout- ini adalah tugas makro.



Selanjutnya, mesin mencapai metode Promise.resolve(). Metode ini ditambahkan ke tumpukan, dan kemudian dijalankan dengan nilai Promise. Callback-nya thenditempatkan dalam antrian mikrotask.



Akhirnya, mesin mencapai metode kedua console.log(). Segera didorong ke stack, itu adalah output ke konsolEnd!, metode ini dihapus dari tumpukan, dan mesin terus berlanjut.



Mesin โ€œmelihatโ€ bahwa tumpukan kosong. Memeriksa antrian tugas. Terletak di sana then. Itu didorong ke tumpukan, nilai janji ditampilkan pada konsol: dalam hal ini, sebuah string Promise!.



Mesin melihat bahwa tumpukan kosong. Dia "terlihat" dalam antrian mikrotasks. Dia juga kosong.

Sudah waktunya untuk memeriksa antrian tugas makro: ada di sana setTimeout. Itu didorong ke tumpukan dan mengembalikan metode console.log(). String adalah output ke konsol 'In timeout!'. setTimeoutdihapus dari tumpukan.



Selesai Sekarang semuanya jatuh pada tempatnya, kan?



Async / tunggu


ES7 memperkenalkan cara baru untuk bekerja dengan kode asinkron di JS. Menggunakan kata kunci asyncdan awaitkita dapat membuat fungsi asinkron yang secara implisit mengembalikan janji. Tapi ... bagaimana kita melakukan ini?

Sebelumnya, kami membahas cara membuat objek secara eksplisit Promise: menggunakan new Promise(() => {}), Promise.resolveatau Promise.reject.

Sebagai gantinya, kita dapat membuat fungsi asinkron yang secara implisit mengembalikan objek yang ditentukan. Ini berarti bahwa kita tidak perlu lagi membuat secara manual Promise.



Fakta bahwa fungsi asinkron secara implisit mengembalikan janji, tentu saja, bagus, tetapi kekuatan fungsi ini sepenuhnya dimanifestasikan saat menggunakan kata kunci await.awaitmembuat fungsi asinkron menunggu janji (nilainya) selesai. Untuk mendapatkan nilai dari janji yang dilakukan, kita harus menetapkan variabel nilai yang diharapkan (ditunggu) dari janji tersebut.

Ternyata kita bisa menunda eksekusi fungsi asinkron? Bagus, tapi ... apa artinya itu?

Mari kita lihat apa yang terjadi ketika Anda menjalankan kode berikut:







Mula-mula mesin melihat console.log. Metode ini didorong ke tumpukan dan ditampilkan di konsol Before function!.



Kemudian fungsi asinkron dipanggil myFunc(), kodenya dieksekusi. Di baris pertama dari kode ini, kita memanggil baris kedua console.logdengan sebuah baris 'In function!'. Metode ini ditambahkan ke tumpukan, nilainya ditampilkan di konsol, dan dihapus dari tumpukan.



Kode fungsi dijalankan selanjutnya. Di baris kedua kami memiliki kata kunci await.

Hal pertama yang terjadi di sini adalah eksekusi nilai yang diharapkan: dalam hal ini, fungsinya one. Itu didorong ke tumpukan dan mengembalikan janji. Setelah janji itu selesai, dan fungsi onemengembalikan nilainya, mesin melihat await.

Setelah itu, eksekusi fungsi asinkron tertunda. Eksekusi fungsi tubuh ditangguhkan, kode yang tersisa dijalankan sebagai mikrotask.



Setelah eksekusi fungsi asinkron ditunda, mesin kembali ke eksekusi kode dalam konteks global.



Setelah semua kode dieksekusi dalam konteks global, loop acara memeriksa mikrotask dan mendeteksi myFunc(). myFunc()didorong ke tumpukan dan dieksekusi.

Variabel resmendapatkan nilai dari janji yang dieksekusi dikembalikan oleh fungsi one. Kami memanggil console.logdengan nilai variabel res: string One!dalam kasus ini. One!Ini ditampilkan di konsol.

Selesai Perhatikan perbedaan antara fungsi asinkron dan metode thenpromis? Kata kunciawaitmenunda pelaksanaan fungsi asinkron. Jika kita menggunakan then, maka tubuh Janji akan terus berjalan.



Ternyata sangat bertele-tele. Jangan khawatir jika Anda merasa tidak aman saat bekerja dengan janji. Butuh beberapa waktu untuk terbiasa dengan mereka. Ini umum untuk semua teknik untuk bekerja dengan kode asinkron di JS.

Lihat juga "Visualisasi pekerjaan pekerja layanan . "

Terima kasih atas waktu Anda. Saya harap itu dihabiskan dengan baik.

All Articles