Kedua jawaban teratas ini salah. Lihatlah deskripsi MDN pada model concurrency dan loop acara , dan akan menjadi jelas apa yang terjadi (sumber daya MDN adalah permata nyata). Dan hanya menggunakan setTimeout
dapat menambahkan masalah yang tidak terduga dalam kode Anda selain "menyelesaikan" masalah kecil ini.
Apa sebenarnya terjadi di sini bukanlah bahwa "browser mungkin belum siap karena konkurensi," atau sesuatu yang berdasarkan pada "setiap baris adalah peristiwa yang ditambahkan ke bagian belakang antrian".
The jsfiddle disediakan oleh DVK memang menggambarkan masalah, tapi penjelasannya untuk itu tidak benar.
Apa yang terjadi dalam kodenya adalah bahwa dia pertama kali memasang pengendali acara ke click
acara tersebut di#do
tombol.
Kemudian, ketika Anda benar-benar mengklik tombol, a message
dibuat merujuk fungsi event handler, yang akan ditambahkan ke message queue
. Ketika event loop
mencapai pesan ini, itu menciptakan aframe
on the stack, dengan pemanggilan fungsi ke pengendali event klik di jsfiddle.
Dan di sinilah mulai menarik. Kami sudah terbiasa menganggap Javascript sebagai tidak sinkron sehingga kami cenderung mengabaikan fakta kecil ini: Setiap frame harus dieksekusi, secara penuh, sebelum frame berikutnya dapat dieksekusi . Tidak ada konkurensi, teman.
Apa artinya ini? Ini berarti bahwa setiap kali suatu fungsi dipanggil dari antrian pesan, ia memblokir antrian sampai tumpukan yang dihasilkannya dikosongkan. Atau, dalam istilah yang lebih umum, itu memblokir sampai fungsi telah kembali. Dan itu memblokir segalanya , termasuk operasi rendering DOM, scrolling, dan yang lainnya. Jika Anda ingin konfirmasi, coba saja tambahkan durasi operasi yang berjalan lama di biola (mis. Jalankan loop luar 10 kali lebih banyak), dan Anda akan melihat bahwa ketika sedang berjalan, Anda tidak dapat menggulir halaman. Jika berjalan cukup lama, browser Anda akan menanyakan apakah Anda ingin mematikan prosesnya, karena itu membuat halaman menjadi tidak responsif. Frame sedang dieksekusi, dan loop acara dan antrian pesan macet sampai selesai.
Jadi mengapa efek samping teks ini tidak diperbarui? Karena ketika Anda telah mengubah nilai elemen dalam DOM - Anda dapat console.log()
nilainya segera setelah mengubahnya dan melihat bahwa itu telah diubah (yang menunjukkan mengapa penjelasan DVK tidak benar) - browser sedang menunggu tumpukan untuk menguras (ituon
fungsi penangan untuk kembali) dan dengan demikian pesan untuk selesai, sehingga pada akhirnya bisa menyelesaikan mengeksekusi pesan yang telah ditambahkan oleh runtime sebagai reaksi terhadap operasi mutasi kami, dan untuk mencerminkan mutasi itu di UI .
Ini karena kami sebenarnya menunggu kode selesai berjalan. Kami belum mengatakan "seseorang mengambil ini dan kemudian memanggil fungsi ini dengan hasilnya, terima kasih, dan sekarang saya sudah selesai imma kembali, lakukan apa pun sekarang," seperti yang biasa kita lakukan dengan Javascript asinkron berbasis acara kami. Kami memasukkan fungsi event handler klik, kami memperbarui elemen DOM, kami memanggil fungsi lain, fungsi lainnya bekerja untuk waktu yang lama dan kemudian kembali, kami kemudian memperbarui elemen DOM yang sama, dan kemudian kami kembali dari fungsi awal, secara efektif mengosongkan tumpukan. Dan kemudian browser dapat membuka pesan berikutnya dalam antrian, yang mungkin merupakan pesan yang dihasilkan oleh kami dengan memicu beberapa jenis acara "on-DOM-mutation" internal.
UI browser tidak dapat (atau memilih untuk tidak) memperbarui UI sampai frame yang sedang dieksekusi selesai (fungsi telah kembali). Secara pribadi, saya pikir ini bukan dengan desain daripada pembatasan.
Mengapa setTimeout
hal itu berhasil? Ia melakukannya, karena ia secara efektif menghilangkan panggilan ke fungsi yang sudah berjalan lama dari kerangkanya sendiri, menjadwalkannya untuk dieksekusi nanti dalam window
konteksnya, sehingga ia sendiri dapat segera kembali dan memungkinkan antrian pesan untuk memproses pesan lainnya. Dan idenya adalah bahwa pesan "saat pembaruan" UI yang telah dipicu oleh kami di Javascript saat mengubah teks di DOM sekarang berada di depan pesan yang antri untuk fungsi jangka panjang, sehingga pembaruan UI terjadi sebelum kami memblokir untuk waktu yang lama.
Perhatikan bahwa a) Fungsi yang berjalan lama masih memblokir semua saat dijalankan, dan b) Anda tidak dijamin bahwa pembaruan UI sebenarnya ada di depannya dalam antrian pesan. Pada browser Chrome Juni 2018 saya, nilai 0
tidak "memperbaiki" masalah yang ditunjukkan oleh biola - 10. Saya sebenarnya agak terhalang oleh ini, karena menurut saya logis bahwa pesan pembaruan UI harus di-antri sebelum itu, karena pemicunya dieksekusi sebelum penjadwalan fungsi yang berjalan lama untuk dijalankan "nanti". Tapi mungkin ada beberapa optimisasi di mesin V8 yang dapat mengganggu, atau mungkin pemahaman saya kurang.
Oke, jadi apa masalahnya dengan menggunakan setTimeout
, dan apa solusi yang lebih baik untuk kasus khusus ini?
Pertama, masalah dengan menggunakan setTimeout
event handler seperti ini, untuk mencoba mengatasi masalah lain, cenderung berantakan dengan kode lain. Ini contoh nyata dari pekerjaan saya:
Seorang kolega, dalam pemahaman yang salah informasi pada loop acara, mencoba "mengaitkan" Javascript dengan meminta beberapa kode rendering templat digunakan setTimeout 0
untuk renderingnya. Dia tidak lagi di sini untuk bertanya, tetapi saya dapat berasumsi bahwa mungkin dia memasukkan timer untuk mengukur kecepatan rendering (yang akan menjadi kedekatan kembali fungsi) dan menemukan bahwa menggunakan pendekatan ini akan membuat respons yang sangat cepat dari fungsi itu.
Masalah pertama sudah jelas; Anda tidak dapat mengaitkan javascript, jadi Anda tidak memenangkan apa pun di sini saat Anda menambahkan kebingungan. Kedua, Anda sekarang telah secara efektif memisahkan rendering templat dari tumpukan kemungkinan pendengar acara yang mungkin berharap bahwa templat yang telah dirender, meskipun mungkin tidak. Perilaku aktual dari fungsi itu sekarang non-deterministik, seperti - tanpa sadar begitu - fungsi apa pun yang akan menjalankannya, atau bergantung padanya. Anda dapat membuat tebakan yang terpelajar, tetapi Anda tidak dapat membuat kode yang benar untuk perilakunya.
"Memperbaiki" saat menulis event handler baru yang bergantung pada logikanya juga digunakan setTimeout 0
. Tapi, itu bukan perbaikan, sulit untuk dipahami, dan tidak menyenangkan untuk men-debug kesalahan yang disebabkan oleh kode seperti ini. Kadang-kadang tidak ada masalah, di lain waktu itu gagal secara konsisten, dan sekali lagi, kadang-kadang ia bekerja dan rusak secara sporadis, tergantung pada kinerja platform saat ini dan apa pun yang terjadi pada saat itu. Inilah mengapa saya pribadi menyarankan untuk tidak menggunakan hack ini (ini adalah hack, dan kita semua harus tahu itu), kecuali Anda benar-benar tahu apa yang Anda lakukan dan apa konsekuensinya.
Tapi apa yang bisa kita lakukan? Yah, seperti yang disarankan oleh artikel MDN yang direferensikan, pisahkan pekerjaan menjadi beberapa pesan (jika Anda bisa) sehingga pesan lain yang di-antri dapat disatukan dengan pekerjaan Anda dan dieksekusi saat dijalankan, atau menggunakan pekerja web, yang dapat menjalankan bersama-sama dengan halaman Anda dan mengembalikan hasil ketika dilakukan dengan perhitungannya.
Oh, dan jika Anda berpikir, "Yah, tidak bisakah saya melakukan panggilan balik pada fungsi yang sudah berjalan lama untuk menjadikannya tidak sinkron ?," lalu tidak. Callback tidak menjadikannya tidak sinkron, itu masih harus menjalankan kode yang sudah berjalan lama sebelum secara eksplisit memanggil panggilan balik Anda.