Dalam kode berbagai proyek, sering kali orang harus beroperasi tepat waktu - misalnya, untuk mengikat logika pengguna dari aplikasi ke waktu saat ini. Victor Khomyakov, Victor
-homyakov, pengembang antarmuka senior, menggambarkan kesalahan khas yang ia temui dalam proyek-proyek di Jawa, C #, dan JavaScript dari berbagai penulis. Mereka menghadapi tugas yang sama: mendapatkan tanggal dan waktu saat ini, mengukur interval, atau mengeksekusi kode secara tidak sinkron.

- Sebelum Yandex, saya bekerja di perusahaan makanan lain. Ini tidak seperti pekerja lepas - saya menulis, lulus, dan lupa. Butuh waktu sangat lama untuk bekerja dengan satu basis kode. Dan saya benar-benar menyaksikan, membaca, menulis banyak kode dalam berbagai bahasa dan melihat banyak hal menarik. Alhasil, saya terlahir dengan tema cerita ini.
Sebagai contoh, saya melihat bahwa dalam proyek yang berbeda dalam bahasa yang berbeda tugas yang sama atau sangat mirip muncul - bekerja dengan tanggal, waktu. Selain pekerjaan itu sendiri, itu bisa menjadi operasi pop-up dalam kode dengan objek tanggal dan waktu.

Ternyata terlepas dari apakah Anda adalah front-end atau back-end, Anda memiliki tugas yang sama untuk bekerja dengan kode asinkron. Jika Anda berada di backend, ini adalah pertanyaan ke database, panggilan jarak jauh. Jika front-end - Anda secara alami memiliki AJAX. Orang yang berbeda dalam proyek yang berbeda menyelesaikan masalah ini dengan cara yang hampir sama, inilah esensi manusia. Dengan tugas yang sama, Anda membuat keputusan yang sama, terlepas dari bahasa yang Anda pikirkan. Dan logis bahwa pada saat yang sama Anda - kami, saya - membuat kesalahan yang sangat mirip.
Apa yang ingin saya bicarakan pada akhirnya? Tentang pola berulang yang terjadi terlepas dari bahasa yang Anda tulis, kesalahan yang mudah dibuat, dan bagaimana tidak membuatnya.
Bagian pertama dikhususkan untuk waktu. Seperti yang Anda tahu, waktu bergerak. Contoh: Anda perlu menulis laporan untuk kemarin, untuk satu hari penuh. Anda membuat permintaan ke database, Anda perlu mendapatkan semua catatan yang tanggalnya lebih besar atau sama dengan kemarin dan kurang dari hari ini. Artinya, Anda mulai dari tanggal "hari ini minus satu hari" dan hingga hari ini, tidak termasuk itu.

Jadi secara linear Anda, secara umum, menulis kode. Tanggal mulai - hari ini minus satu hari, tanggal akhir - hari ini. Tampaknya semuanya bekerja, tetapi kemudian tepat di tengah malam Anda memiliki hal yang aneh. Tanggal mulai Anda di sini. Tanggal mulai dikurangi satu hari - ternyata ini. Setelah itu, untuk beberapa alasan, tanggal akhir laporan sama sekali berbeda.

Anda, atau lebih tepatnya bos Anda, mendapatkan laporan selama dua hari, bukan satu. Manajer teknis dan manajer datang, mengeluh dan dengan sopan menawarkan Anda untuk pindah ke tim lain dalam enam bulan.

Tapi kemudian Anda diperkaya dengan pengetahuan baru. Anda mengerti bahwa waktu tidak berhenti. Artinya, memanggil Date.now () dua kali atau mendapatkan Date () baru, Anda tidak berharap untuk mendapatkan nilai yang sama. Terkadang mungkin sama, tetapi mungkin tidak sama. Dengan demikian, jika Anda memiliki satu metode, salah satu bagian logika, maka kemungkinan besar hanya ada satu panggilan ke Date.now () atau mendapatkan Date baru (), titik saat ini dalam waktu.
Atau mari kita beralih ke sisi lain: dalam aliran pemrosesan data, semua nilai yang terkait dengan makna - awal dan akhir laporan - harus dihitung secara ketat dari satu objek. Bukan dari dua yang serupa, misalnya, tetapi dari tepat satu. Anda diperkaya dengan pengetahuan baru ini, pindah ke tim baru. Di sana, orang lebih peduli tentang kecepatan dan kinerja kode.

Dan Anda ditawari untuk overlay kode dengan logging, untuk mengukur berapa lama waktu yang dibutuhkan operasi. Jika ini adalah operasi yang sulit, penting bahwa itu tidak memperlambat klien. Jika Anda menulis sesuatu di backend, di Node, itu juga transaksi yang sulit, maka mereka bertanya kepada Anda: "Tolong tuliskan pada log berapa lama, dan kemudian kami akan menghitung bagaimana perilaku pelanggan kami tergantung pada agen pengguna."
Lalu dua bos yang sudah baru mendatangi Anda dan menunjukkan entri pada Anda, di mana Anda tiba-tiba mencatat waktu negatif. Dan mereka juga dengan sopan menawarkan Anda untuk pindah ke tim lain dalam enam bulan.

Anda mendapatkan pengetahuan yang berharga bahwa, pada kenyataannya, metode untuk mendapatkan tanggal, waktu yang Anda gunakan - mereka hanya menunjukkan apa yang Anda miliki di jam sistem operasi Anda. Mereka juga tidak menjamin perubahan seragam. Yaitu, di detik kedua waktu nyata Anda, Date.now Anda () dapat keduanya melompat satu detik, dan sedikit lebih banyak - sedikit lebih sedikit. Dan pada prinsipnya, mereka umumnya tidak menjamin perubahan yang monoton. Yaitu, seperti dalam contoh ini, itu mungkin tiba-tiba menurun, nilai Date.now sekarang tiba-tiba menurun.
Apa alasannya Sinkronisasi waktu. Pada sistem mirip Linux, ada yang namanya daemon NTP, yang menyinkronkan jam sistem operasi Anda dengan jam yang tepat di Internet. Dan jika Anda memiliki lag atau lead, itu dapat memperlambat atau secara artifisial mempercepat jam tangan Anda, atau jika Anda memiliki jeda waktu yang sangat besar, ia akan memahami bahwa ia tidak akan dapat menangkap waktu yang tepat dengan langkah-langkah yang tidak mencolok, dan hanya dengan satu lompatan mengubahnya. Akibatnya, Anda mendapat celah dalam pembacaan jam tangan Anda.
Atau Anda dapat sangat menyulitkannya: pengguna itu sendiri, yang memiliki kendali atas jam, ia mungkin juga ingin hanya mengubah jam. Dia benar-benar menginginkannya. Dan kita tidak punya hak untuk menghentikannya. Dan di log kita mendapatkan istirahat. Dan, karenanya, solusi untuk masalah ini juga sudah ada. Sederhana: ada pemasok waktu. Jika Anda menggunakan browser, maka ini adalah performance.now (), jika Anda menulis di Node, maka ada Timer Resolusi Tinggi, yang keduanya memiliki sifat keseragaman dan monoton. Artinya, ini pemasok perangko waktu, mereka selalu hanya meningkat, dan pada saat yang sama merata dalam satu detik waktu nyata.

Backend memiliki masalah yang sama. Tidak masalah bahasa apa yang Anda tulis. Misalnya, Anda dapat mencari arloji konsisten yang monoton, dan masalah memberi Anda, di mana hampir semua bahasa disajikan. Ada masalah yang sama di Rust. Ada juga rasa sakit dari seorang programmer yang berada di Python, dan di Jawa, dan dalam bahasa lain. Dalam bahasa-bahasa ini, orang juga telah menginjak menyapu, masalah ini diketahui, ada solusinya. Misalnya, untuk Jawa ada panggilan yang memiliki sifat keseragaman dan keseragaman yang sama.
Jika Anda memiliki sistem terdistribusi, layanan Microsoft yang modis, misalnya, maka itu masih lebih rumit. Ada N layanan yang berbeda pada N mesin yang berbeda, jam di mana, secara umum, bahkan tidak pernah bisa menyatu dengan satu indikasi pada prinsipnya, tidak ada yang bahkan diharapkan.
Dan jika Anda memiliki masalah tindakan logging, maka Anda dapat mencatat hanya vektor waktu. Anda, ternyata, mencatat N kali dari N sistem yang terlibat dalam memproses satu permintaan. Atau Anda hanya pergi ke penghitung abstrak, yang hanya meningkat: 1, 2, 3, 4, 5, itu hanya mencentang secara merata pada setiap mesin dengan operasi ini. Dan Anda menulis penghitung seperti itu untuk menautkan semua tahap memproses permintaan Anda pada mesin yang berbeda, dan untuk mendapatkan pemahaman tentang kapan, apa yang terjadi, dalam urutan apa.
Juga jangan lupa: jika Anda front-end atau back-end, yang bekerja dengan front-end dalam koneksi dekat, maka front-end kami plus back-end juga merupakan sistem terdistribusi. Dan jika Anda juga tertarik pada beberapa jenis sesi pekerjaan klien yang sulit, harap juga coba, pertama, untuk tidak mencampuradukkannya, ketika Anda melihat log, jam berapa Anda melihat: โdi sini adalah catatan bahwa operasi ini terjadi berkali-kali "- apakah Anda melihat waktu server atau waktu klien?" Dan kedua, cobalah untuk mengumpulkan dua kali, karena, seperti yang saya katakan, waktu bisa pergi ke arah yang berbeda.
Cukup waktu. Bagian kedua lebih tidak menentu.

Berikut ini sebuah contoh. Ada elemen antarmuka yang sangat berguna ketika pengguna tidak tahu persis apa yang diinginkannya. Ini disebut sarankan, atau pelengkapan otomatis. Kami dapat memberi tahu dia opsi untuk melanjutkan permintaan. Artinya, bagi pengguna ini adalah manfaat yang sangat besar. Jauh lebih nyaman baginya untuk bekerja ketika kami segera menunjukkan kepadanya bahwa kami tahu apa yang bisa kami rekrut lebih lanjut.
Tetapi, sayangnya, jika kita mendapatkan jaringan yang sedikit lambat, atau jika backend, yang memberikan jawaban, opsi untuk kelanjutan, melambat, maka kita bisa mendapatkan efek yang menarik. Pengguna mengetik, mengetik, lalu jawaban yang benar datang, kami melihatnya, dan kemudian semuanya rusak. Untuk beberapa alasan, kami sama sekali tidak melihat apa yang ingin kami lihat. Di sini kita melihat jawaban yang benar, dan segera beberapa omong kosong untuk semacam negara perantara. Sekali lagi, kesakitan dan penderitaan. Atasan kami mendatangi kami dan meminta kami untuk memperbaiki bug ini.

Kami mulai mengerti. Apa yang kita dapatkan Saat pengguna mengetik teksnya, kami mendapatkan generasi permintaan asinkron berurutan. Artinya, apa yang berhasil diketiknya, kami kirim ke backend. Dia memanggil lebih lanjut, kami mengirim permintaan kedua untuk backend, dan tidak ada yang pernah menjamin kami bahwa panggilan balik kami akan dipanggil dalam urutan yang persis sama.

Ini adalah opsi query dan callback yang memungkinkan. Yang paling jelas, ketika kita menulis, kita berpikir: mereka mengirim permintaan pertama, menerima tanggapan pertama, mengirim permintaan kedua, menerima jawabannya. Jika pengguna mengetik dengan sangat cepat, maka kita dapat memikirkan opsi kedua yang berhasil kami kirim permintaan pertama, pengguna berhasil mengetik sesuatu sebelum menerima jawaban pertama. Lalu datang jawaban pertama, jawaban kedua. Dan inilah yang kami lihat di video, ketika saran tidak berfungsi dengan benar, ini adalah opsi ketiga, yang sering dilupakan, bahwa tidak ada yang menjamin urutan jawaban, secara umum.

Dan di vendor front-end masalah ini sangat umum jika Anda mengembangkan antarmuka. Secara khusus, contoh dengan sarankan, dengan autocomplete, yang baru saja kita lihat. Artinya, ada aliran permintaan, dan ada aliran tanggapan yang tiba-tiba tiba.
Jika Anda memiliki tab. Angkat tanganmu, siapa di GitHub yang melakukan setidaknya satu permintaan tarik? Anda ingat bahwa di sana, sebenarnya, antarmuka tab didasarkan, yaitu, ada tab di mana ada urutan komentar, ada tab dengan komit, dan ada tab dengan kode itu sendiri. Ini adalah antarmuka tab. Dan jika Anda beralih ke tab tetangga, isinya dimuat secara tidak sinkron untuk pertama kalinya.
Jika Anda mengklik dengan cepat pada tab yang berbeda, mungkin Anda mengaktifkannya, dan kemudian Anda melihat pemuatan konten berkedip. Dan pada akhirnya, itu bukan fakta bahwa Anda akan melihat isi tab yang benar, jika Anda benar, tentu saja, jangan menulis sendiri.
Misalnya, jika Anda memiliki toko, jika Anda dengan cepat menyeret barang ke keranjang. Beberapa pengguna yang cepat dan tajam menyeret sepuluh barang, dan kemudian dia melihat bagaimana harganya berkedip dan, secara relatif, 100 rubel, 10 rubel, 50 rubel, 75 rubel, dan berhenti pada satu rubel. Dia tidak mempercayaimu, dia berpikir bahwa kamu menulis dengan buruk, kamu ingin menipu dia, dan meninggalkan toko kamu tanpa membeli apapun.
Sebuah contoh Jika Anda memiliki semacam scrum atau kanban atau apa pun dan Anda menggunakan papan elektronik untuk menarik dan menjatuhkan kartu, Anda mungkin melewatkan kartu setidaknya satu kali ketika Anda menyeretnya, jatuhkan ke kolom yang salah. Apakah ini terjadi? Tentu saja, Anda menangkap diri Anda dan segera mengambilnya dengan tajam dan menyeretnya ke tempat yang seharusnya. Dalam hal ini, Anda dengan sangat cepat menghasilkan dua kueri. Dan dalam sistem yang berbeda ada bug yang muncul setelah itu. Anda menyeretnya ke kolom yang benar - jawaban atas permintaan pertama tiba, dan kartu kembali melompat ke kolom tempat Anda mentransfernya. Ternyata sangat jelek.

Apa itu moral? Misalkan Anda memiliki sumber dengan jenis permintaan yang sama. Kemudian, jika memungkinkan, jika permintaan berikutnya tiba, interupsi semua permintaan yang tidak lengkap agar tidak menyia-nyiakan sumber daya, sehingga backend tahu - Anda tidak membutuhkannya lagi.
Dengan demikian, saat memproses respons, Anda juga mengontrol semuanya. Dan jika suatu tanggapan datang pada permintaan sebelumnya yang tidak Anda butuhkan, Anda juga secara eksplisit mengabaikannya.

Dengan demikian, masalahnya sudah ada sejak lama, dan solusinya juga sudah ada. Misalnya, di perpustakaan RxJS. Ini adalah contoh langsung dari dokumentasi, Hello world, bagaimana menulis autocomplete yang benar. Tepat di luar kotak ada pengabaian untuk jawaban atas permintaan yang salah yang lebih lama.

Jika Anda menulis di Redux dan Redux-Saga, itu ada juga, secara umum, dan semuanya juga ditulis dalam dokumentasi. Tapi ada yang terkubur dalam, dan jelas tidak dikatakan bahwa itu adalah bug dan kami memperbaikinya seperti itu. Hanya deskripsi saja.
Karena kami telah beralih ke Bereaksi, kami akan bergerak mendekatinya.

Ini adalah bagian dari kode nyata yang kami miliki di repositori kami. Seseorang menggambar kartu bersama kami. Dan tolong, ketika Anda mendapatkan peta, sangat disarankan untuk menunjukkan tanda di mana pengguna berada. Tapi ini semua terjadi di browser. Artinya, jika Anda mengaktifkan geolokasi, maka kami bisa mendapatkan koordinat Anda, dan kami dapat langsung menunjukkan di mana Anda berada di peta.
Jika geolokasi tidak diizinkan, atau jika terjadi kesalahan di sana, maka disarankan bagi kami untuk menunjukkan semacam dadu dengan kesalahan. Yaitu, di sini kami menunjukkan dadu, bahwa kami tidak dapat menunjukkan di mana Anda berada, bung, dan setelah tiga detik kami melepasnya, dadu ini. Anda berhasil membaca, mungkin. Selain itu, objek bergerak, seperti mati dan menghilang, itu segera menarik perhatian, dan Anda akan segera melihatnya, membacanya.
Tetapi jika Anda hati-hati melihat apa yang terjadi dalam kode ini, maka kami mengubah status komponen kami setelah tiga detik. Apa pun bisa terjadi dalam tiga detik ini. Termasuk pengguna dapat menutup kartu ini untuk waktu yang lama, dan komponen Anda akan dilepas, bersihkan kondisinya.

Dengan demikian, Anda menembak diri sendiri di kaki, dan menembak pada lintasan balistik, yang akan berakhir dalam tiga detik. Dan apa yang harus dilakukan? Jangan lupa bahwa jika Anda melakukan operasi yang tertunda, Anda dapat membersihkannya dengan benar dengan meng-unmount. Dan dalam kerangka lain dengan metode siklus hidup lainnya, hal yang sama masuk akal. Ketika Anda memiliki semacam penghancuran, penghancuran, sesuatu yang lain, tidak terpasang, Anda harus ingat dengan benar untuk membersihkan hal-hal seperti itu.

Di mana di browser kode Anda dapat ditunda? Ada beberapa hal seperti throttle dan debounce. Mereka memiliki setTimeout, setInterval di bawah tenda, sesuatu yang sudah saya tunjukkan. Masih ada requestAnimationFrame, masih ada requestIdleCallback. Dan permintaan AJAX juga - Panggilan balik permintaan AJAX dapat disebut ditangguhkan. Jangan lupakan mereka juga, mereka juga perlu dibersihkan.

Dan jika kita menyelam lebih jauh satu tingkat, kita akan memahami bahwa awalnya seluruh masalah diabstraksikan sedemikian rupa sehingga kita memiliki semacam komponen dengan semacam siklus hidup dan kita menunda telepon. Kami membuat di dalam objek berumur panjang, yang memiliki umur lebih panjang dari yang asli. Yaitu, ada dua objek dengan siklus hidup yang tidak cocok, dengan rentang hidup yang tidak cocok. Dan dari dua bug ini segera mengalir.
Yang pertama adalah apa yang kita miliki sekarang: objek berumur panjang memegang tautan ke fungsi Anda dan menyebutnya, meskipun Anda sudah mati. Dan yang kedua adalah kebocoran memori yang terkait. Artinya, sekali lagi, objek berumur panjang menyimpan tautan ke kode Anda dan tidak mengizinkannya dibersihkan, dikumpulkan dari memori.
Bagian ketiga adalah kebalikan dari yang kedua. Dia, sebaliknya, adalah tentang sinkronisasi.

Ada, seperti biasa, rantai janji - lalu, lalu, kemudian sesuatu di sana. Dan dalam kode ini, jika Anda melihat, jika Anda menulis dengan rapi, jika Anda seorang pendukung, atau telah mendengar setidaknya tentang pendekatan fungsional, tentang fungsi murni, tentang tidak adanya efek samping, maka Anda dapat memahami bahwa sesuatu dapat dilakukan dalam kode ini mempercepat.
Karena kedua permintaan ini tidak sinkron, keduanya jelas tidak saling tergantung. Jika Anda tidak yakin tentang ini, itu berarti Anda menulis sesuatu yang salah, yaitu, di sana Anda jelas memiliki semacam efek samping, keadaan global, dan sebagainya. Jika Anda menulis dengan baik, maka segera menjadi jelas bagi Anda. Di sini, omong-omong, adalah keuntungan yang jelas dari kemurnian fungsi, dari kurangnya efek samping. Karena di sini, ketika membaca kode ini, Anda memahami bahwa mereka dapat diparalelkan. Mereka independen satu sama lain. Dan, secara umum, mereka bahkan dapat ditukar, kemungkinan besar.

Ini dilakukan seperti ini. Kami menjalankan dua kueri secara paralel, menunggu hingga selesai, dan kemudian jalankan kode berikut. Artinya, untung apa? Faktanya, pertama, kode kami berjalan lebih cepat, kami tidak menunggu satu permintaan untuk memulai yang kedua. Dan kita akan jatuh lebih cepat. Jika kami memiliki kesalahan dalam permintaan kedua, maka kami tidak akan membuang waktu menunggu permintaan pertama dieksekusi agar segera jatuh pada permintaan kedua.

Untuk kelengkapan, apa lagi yang kami miliki di API Janji? Inilah Promise.all (), yang menjalankan semua permintaan secara paralel, dan menunggu eksekusi. Ada Promise.race (), yang sedang menunggu yang pertama dari mereka untuk berhasil. Dan, secara umum, tidak ada yang lain dalam API standar.

Kami sudah mengerti bahwa jika ada masalah, maka seseorang telah menyelesaikannya untuk kami. Ada perpustakaan Async yang memiliki banyak pilihan untuk mengelola tugas-tugas asinkron. Ada metode untuk menjalankan tugas asinkron secara paralel. Ada metode untuk menjalankan secara berurutan satu demi satu. Ada metode untuk mengatur iterator asinkron. Yaitu, Anda tahu bahwa Anda memiliki, katakanlah, sebuah array tempat Anda dapat menjalankan forEach (). Tetapi jika Anda perlu memanggil fungsi asinkron di forEach (), maka Anda langsung memiliki masalah dan Anda menolak forEach () dan menulis sesuatu sendiri, atau menggunakan pustaka siap pakai yang siap menggunakan hal-hal yang tidak sinkron sama. Anda mengerti, panggil peta () dengan semacam iterator asinkron, panggil untukEach () - sudah ada di dalam kotak.

Alternatif lain adalah perpustakaan bluebird. Ada, sebagaimana mereka menyebutnya, Janji yang benar.any (). , , : N , N - , , . , , . .
Promise.race(), , promise , , , . . Promise.any() โ reject. . reject , resolve , , . . promise โ , .
, map, reduce, each, filter . API , Async JS, . promise . , , , promise. .
promise? , async/await.

. . . ,
ยซยป . , webdriver. , , - , . . . webdriver.
, await. . , - . await, โ , , ! .

โ Promise.all(). , await.

: await , then . , .
, . : await, , โ , .
, , :
, -, :
? , โ Lodash, RxJS . . , . , - . . โ , , . .