ProHoster > Blog > Pentadbiran > Pengaturcaraan tak segerak dalam JavaScript (Panggil Balik, Janji, RxJs)
Pengaturcaraan tak segerak dalam JavaScript (Panggil Balik, Janji, RxJs)
Hai semua. Sergey Omelnitsky sedang berhubung. Tidak lama dahulu saya menjadi tuan rumah aliran pada pengaturcaraan reaktif, di mana saya bercakap tentang asynchrony dalam JavaScript. Hari ini saya ingin mengambil nota mengenai bahan ini.
Tetapi sebelum kita memulakan bahan utama, kita perlu membuat nota pengenalan. Jadi mari kita mulakan dengan definisi: apakah tindanan dan baris gilir?
Timbunan ialah koleksi yang elemennya diperoleh berdasarkan LIFO masuk terakhir, keluar dahulu
Beratur ialah koleksi yang elemennya diperolehi berdasarkan asas FIFO masuk dahulu, keluar dahulu
Okay, mari kita sambung.
JavaScript ialah bahasa pengaturcaraan satu benang. Ini bermakna bahawa hanya terdapat satu utas pelaksanaan dan satu timbunan di mana fungsi diletakkan gilir untuk pelaksanaan. Oleh itu, JavaScript hanya boleh melaksanakan satu operasi pada satu masa, manakala operasi lain akan menunggu giliran mereka pada tindanan sehingga mereka dipanggil.
Timbunan panggilan ialah struktur data yang, secara ringkasnya, merekodkan maklumat tentang tempat dalam program di mana kita berada. Jika kita masuk ke dalam fungsi, kita menolak kemasukannya ke bahagian atas timbunan. Apabila kami kembali daripada fungsi, kami mengeluarkan elemen paling atas dari timbunan dan berakhir di tempat yang kami panggil fungsi itu. Ini sahaja yang boleh dilakukan oleh timbunan. Dan kini soalan yang sangat menarik. Bagaimanakah asynchrony berfungsi dalam JavasScript?
Malah, sebagai tambahan kepada timbunan, pelayar mempunyai baris gilir khas untuk bekerja dengan apa yang dipanggil WebAPI. Fungsi dalam baris gilir ini akan dilaksanakan mengikut tertib hanya selepas tindanan telah dikosongkan sepenuhnya. Hanya selepas ini mereka ditolak dari baris gilir ke timbunan untuk dilaksanakan. Jika terdapat sekurang-kurangnya satu elemen pada tindanan pada masa ini, maka ia tidak boleh ditambah pada tindanan. Justru kerana inilah panggilan fungsi mengikut tamat masa selalunya tidak tepat pada masanya, kerana fungsi itu tidak boleh pergi dari baris gilir ke timbunan semasa ia penuh.
Mari lihat contoh berikut dan mulakan dengan pelaksanaan langkah demi langkahnya. Mari kita lihat juga apa yang berlaku dalam sistem.
1) Tiada apa yang berlaku lagi. Konsol penyemak imbas jelas, timbunan panggilan kosong.
2) Kemudian arahan console.log('Hi') ditambahkan pada timbunan panggilan.
3) Dan ia dipenuhi
4) Kemudian console.log('Hi') dialih keluar daripada timbunan panggilan.
5) Sekarang beralih kepada perintah setTimeout(function cb1() {β¦ }). Ia ditambah pada timbunan panggilan.
6) Perintah setTimeout(function cb1() {β¦ }) dilaksanakan. Penyemak imbas mencipta pemasa yang merupakan sebahagian daripada API Web. Ia akan melakukan kira detik.
7) Perintah setTimeout(function cb1() {... }) telah menyelesaikan kerjanya dan dialih keluar daripada timbunan panggilan.
8) Perintah console.log('Bye') ditambahkan pada timbunan panggilan.
9) Perintah console.log('Bye') dilaksanakan.
10) Perintah console.log('Bye') dialih keluar daripada timbunan panggilan.
11) Selepas sekurang-kurangnya 5000 ms berlalu, pemasa ditamatkan dan meletakkan panggilan balik cb1 dalam baris gilir panggil balik.
12) Gelung acara mengambil fungsi cb1 daripada baris gilir panggilan balik dan meletakkannya pada timbunan panggilan.
13) Fungsi cb1 dilaksanakan dan menambah console.log('cb1') pada timbunan panggilan.
14) Perintah console.log('cb1') dilaksanakan.
15) Perintah console.log('cb1') dialih keluar daripada timbunan panggilan.
16) Fungsi cb1 dikeluarkan daripada timbunan panggilan.
Mari kita lihat contoh dalam dinamik:
Nah, kami melihat cara tak segerak dilaksanakan dalam JavaScript. Sekarang mari kita bercakap secara ringkas tentang evolusi kod tak segerak.
Evolusi kod tak segerak.
a(function (resultsFromA) {
b(resultsFromA, function (resultsFromB) {
c(resultsFromB, function (resultsFromC) {
d(resultsFromC, function (resultsFromD) {
e(resultsFromD, function (resultsFromE) {
f(resultsFromE, function (resultsFromF) {
console.log(resultsFromF);
})
})
})
})
})
});
Pengaturcaraan tak segerak seperti yang kita ketahui dalam JavaScript hanya boleh dilaksanakan oleh fungsi. Mereka boleh dihantar seperti mana-mana pembolehubah lain ke fungsi lain. Beginilah cara panggilan balik dilahirkan. Dan ia adalah sejuk, menyeronokkan dan suka bermain, sehingga ia bertukar menjadi kesedihan, sayu dan kesedihan. kenapa? Ia mudah:
Apabila kerumitan kod meningkat, projek itu dengan cepat bertukar menjadi blok yang tidak jelas, berulang kali bersarang - "neraka panggilan balik".
Pengendalian ralat boleh menjadi mudah terlepas.
Anda tidak boleh mengembalikan ungkapan dengan pulangan.
Dengan kedatangan Janji, keadaan menjadi lebih baik sedikit.
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 2000);
}).then((result) => {
alert(result);
return result + 2;
}).then((result) => {
throw new Error('FAILED HERE');
alert(result);
return result + 2;
}).then((result) => {
alert(result);
return result + 2;
}).catch((e) => {
console.log('error: ', e);
});
Rantaian janji muncul, yang meningkatkan kebolehbacaan kod
Kaedah berasingan untuk menangkap ralat telah muncul
Menambah kemungkinan pelaksanaan selari menggunakan Promise.all
Kita boleh menyelesaikan asynchrony bersarang menggunakan async/wait
Tetapi janji ada hadnya. Sebagai contoh, janji tidak boleh dibatalkan tanpa menari dengan tamborin, dan apa yang paling penting ialah ia berfungsi dengan satu nilai.
Nah, kami telah menghampiri pengaturcaraan reaktif dengan lancar. Penat? Nasib baik, anda boleh pergi membuat teh, fikirkan tentangnya dan kembali untuk membaca lebih lanjut. Dan saya akan teruskan.
Pengaturcaraan reaktifβialah paradigma pengaturcaraan yang tertumpu pada aliran data dan perambatan perubahan. Mari kita lihat lebih dekat apa itu aliran data.
Bayangkan kita mempunyai medan input. Kami sedang mencipta tatasusunan dan untuk setiap kekunci acara input kami akan menyimpan acara itu dalam tatasusunan kami. Pada masa yang sama, saya ingin ambil perhatian bahawa tatasusunan kami diisih mengikut masa, i.e. indeks peristiwa kemudian adalah lebih besar daripada indeks yang lebih awal. Tatasusunan sedemikian ialah model ringkas aliran data, tetapi ia belum lagi aliran. Agar tatasusunan ini dipanggil strim dengan selamat, ia mesti boleh memberitahu pelanggan bahawa data baharu telah tiba di dalamnya. Oleh itu kita sampai kepada definisi aliran.
Aliranβialah tatasusunan data yang diisih mengikut masa yang boleh menunjukkan bahawa data telah berubah. Sekarang bayangkan betapa senangnya menulis kod di mana satu tindakan memerlukan panggilan beberapa acara dalam bahagian kod yang berlainan. Kami hanya melanggan strim dan ia akan memberitahu kami apabila perubahan berlaku. Dan perpustakaan RxJs boleh melakukan ini.
RxJS ialah perpustakaan untuk bekerja dengan program tak segerak dan berasaskan acara menggunakan jujukan yang boleh diperhatikan. Perpustakaan menyediakan jenis asas Boleh diperhatikan, beberapa jenis tambahan (Pemerhati, Penjadual, Subjek) dan pengendali untuk bekerja dengan acara seperti koleksi (peta, tapis, kurangkan, setiap dan yang serupa daripada JavaScript Array).
Jom fahami konsep asas perpustakaan ini.
Boleh diperhatikan, Pemerhati, Pengeluar
Boleh diperhatikan ialah jenis asas pertama yang akan kita lihat. Kelas ini mengandungi bahagian utama pelaksanaan RxJs. Ia dikaitkan dengan aliran yang boleh diperhatikan, yang boleh dilanggan menggunakan kaedah langgan.
Observable melaksanakan mekanisme pembantu untuk membuat kemas kini, yang dipanggil Pemerhati. Sumber nilai untuk Pemerhati dipanggil Pengeluar. Ini boleh menjadi tatasusunan, iterator, soket web, beberapa jenis acara, dsb. Jadi kita boleh katakan bahawa boleh diperhatikan adalah konduktor antara Pengeluar dan Pemerhati.
Observable mengendalikan tiga jenis peristiwa Pemerhati:
seterusnya β data baharu
ralat - ralat jika urutan berakhir kerana pengecualian. peristiwa ini juga membayangkan penyiapan urutan.
lengkap β isyarat tentang penyiapan urutan. Ini bermakna tiada lagi data baharu.
Mari lihat demo:
Pada mulanya kita akan memproses nilai 1, 2, 3, dan selepas 1 saat. kami akan mendapat 4 dan menamatkan aliran kami.
Berfikir dengan lantang
Dan kemudian saya menyedari bahawa menceritakannya lebih menarik daripada menulis tentangnya. π
langganan
Apabila kami melanggan aliran, kami mencipta kelas baharu langgananyang memberi kita keupayaan untuk berhenti melanggan menggunakan kaedah tersebut unsubscribe. Kami juga boleh mengumpulkan langganan menggunakan kaedah tersebut menambah. Nah, adalah logik bahawa kita boleh menyahhimpunkan benang yang digunakan mengeluarkan. Kaedah tambah dan buang menerima langganan lain sebagai input. Saya ingin ambil perhatian bahawa apabila kami menyahlanggan, kami menyahlanggan semua langganan kanak-kanak seolah-olah mereka telah memanggil kaedah berhenti melanggan. Teruskan.
Jenis-jenis aliran
PANAS
SEJUK
Pengeluar dicipta di luar boleh diperhatikan
Pengeluar dicipta di dalam boleh diperhatikan
Data dipindahkan pada masa yang boleh diperhatikan dibuat
Data disediakan semasa langganan
Perlu logik tambahan untuk berhenti melanggan
Benang ditamatkan dengan sendirinya
Menggunakan perhubungan satu-dengan-banyak
Menggunakan perhubungan satu dengan satu
Semua langganan mempunyai maksud yang sama
Langganan adalah bebas
Data boleh hilang jika anda tidak mempunyai langganan
Mengeluarkan semula semua nilai strim untuk langganan baharu
Untuk memberikan analogi, saya akan menganggap aliran hangat sebagai filem dalam teater. Pada titik masa anda tiba, dari saat itu anda mula menonton. Saya akan membandingkan aliran sejuk dengan panggilan dalam teknologi. sokongan. Mana-mana pemanggil mendengar rakaman mel suara dari awal hingga akhir, tetapi anda boleh menutup telefon menggunakan berhenti melanggan.
Saya ingin ambil perhatian bahawa terdapat juga apa yang dipanggil aliran hangat (saya telah menemui definisi ini sangat jarang dan hanya dalam komuniti asing) - ini adalah aliran yang berubah daripada aliran sejuk kepada aliran panas. Timbul persoalan - di mana hendak digunakan)) Saya akan memberikan contoh dari amalan.
Saya bekerja dengan Angular. Dia aktif menggunakan rxjs. Untuk menerima data ke pelayan, saya mengharapkan benang sejuk dan menggunakan benang ini dalam templat menggunakan asyncPipe. Jika saya menggunakan paip ini beberapa kali, maka, kembali kepada definisi aliran sejuk, setiap paip akan meminta data dari pelayan, yang paling pelik untuk dikatakan. Dan jika saya menukar aliran sejuk kepada aliran hangat, maka permintaan itu akan berlaku sekali.
Secara umum, memahami jenis aliran agak sukar untuk pemula, tetapi penting.
Operator
return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`)
.pipe(
tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))),
map(({ data }: TradeCompanyList) => data)
);
Operator memberi kami keupayaan untuk mengembangkan keupayaan kami untuk bekerja dengan strim. Mereka membantu mengawal peristiwa yang berlaku dalam Observable. Kami akan melihat beberapa yang paling popular, dan butiran lanjut tentang pengendali boleh didapati menggunakan pautan dalam maklumat berguna.
Operator - daripada
Mari kita mulakan dengan pengendali tambahan. Ia mencipta Observable berdasarkan nilai mudah.
Operator - penapis
Operator penapis, seperti namanya, menapis isyarat strim. Jika operator mengembalikan benar, ia melangkau lebih jauh.
Operator - ambil
ambil β Mengambil nilai bilangan pemancar, selepas itu benang berakhir.
Operator - debounceTime
debounceTime - membuang nilai yang dipancarkan yang berada dalam selang masa yang ditentukan antara output - selepas selang masa berlalu, memancarkan nilai terakhir.
Operator combineLatest agak serupa dengan promise.all. Ia menggabungkan berbilang benang menjadi satu. Selepas setiap benang membuat sekurang-kurangnya satu pelepasan, kami mendapat nilai terkini daripada setiap benang dalam bentuk tatasusunan. Selanjutnya, selepas sebarang pelepasan daripada aliran yang digabungkan, ia akan memberikan nilai baharu.
Zip - Menunggu nilai daripada setiap utas dan membentuk tatasusunan berdasarkan nilai ini. Jika nilai tidak datang dari mana-mana benang, maka kumpulan itu tidak akan terbentuk.
Pengendali paip membolehkan anda melakukan kesan sampingan, iaitu, sebarang tindakan yang tidak menjejaskan urutan.
Pengendali utiliti kongsi boleh menukar aliran sejuk kepada aliran panas.
Kami sudah selesai dengan pengendali. Mari kita beralih kepada Subjek.
Berfikir dengan lantang
Dan kemudian saya pergi untuk minum teh. Saya bosan dengan contoh-contoh ini π
Keluarga subjek
Keluarga subjek ialah contoh utama aliran panas. Kelas ini adalah sejenis hibrid yang bertindak serentak sebagai pemerhati dan pemerhati. Memandangkan subjek adalah topik hangat, anda perlu berhenti melanggannya. Jika kita bercakap tentang kaedah utama, maka ini adalah:
seterusnya β pemindahan data baharu ke strim
ralat - ralat dan penamatan benang
lengkap β siap benang
langgan β langgan aliran
berhenti melanggan β berhenti melanggan daripada strim
asObservable β berubah menjadi pemerhati
toPromise β berubah menjadi janji
Terdapat 4 5 jenis mata pelajaran.
Berfikir dengan lantang
Terdapat 4 orang bercakap di aliran, tetapi ternyata mereka menambah seorang lagi. Seperti yang mereka katakan, hidup dan belajar.
Subjek Mudah new Subject()β jenis mata pelajaran yang paling mudah. Dicipta tanpa parameter. Menghantar nilai yang diterima hanya selepas langganan.
Subjek Tingkah Laku new BehaviorSubject( defaultData<T> ) β pada pendapat saya, jenis subjek yang paling biasa. Input mengambil nilai lalai. Sentiasa simpan data isu terakhir, yang dihantar apabila melanggan. Kelas ini juga mempunyai kaedah nilai yang berguna, yang mengembalikan nilai semasa aliran.
Main semula Subjek new ReplaySubject(bufferSize?: number, windowTime?: number) β Input secara pilihan boleh mengambil sebagai argumen pertama saiz penimbal nilai yang akan disimpan dalam dirinya sendiri, dan sebagai detik masa di mana kita memerlukan perubahan.
Subjek Async new AsyncSubject() β tiada apa yang berlaku semasa melanggan, dan nilai akan dikembalikan hanya apabila selesai. Hanya nilai terakhir strim akan dikembalikan.
WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) β Dokumentasi senyap tentang dia dan saya melihatnya buat kali pertama. Jika sesiapa tahu apa yang dia lakukan, sila tulis dan kami akan menambahnya.
Fuh. Nah, kami telah membincangkan semua yang saya ingin beritahu anda hari ini. Saya harap maklumat ini berguna. Anda boleh membaca sendiri senarai rujukan dalam tab maklumat berguna.