Mengapa setTimeout (fn, 0) terkadang berguna?

Baru-baru ini, saya menemukan kesalahan yang agak tidak menyenangkan di mana kode dimuat <select> secara dinamis melalui JavaScript. <select> dimuat secara dinamis ini memiliki nilai yang dipilih sebelumnya. Di IE6, kami sudah memiliki kode untuk memperbaiki <option> dipilih, karena kadang-kadang nilai <select> selectedIndex tidak akan disinkronkan dengan atribut index <option> dipilih, seperti yang ditunjukkan di bawah ini:

 field.selectedIndex = element.index; 

Namun, kode ini tidak berfungsi. Meskipun bidangIndex yang selectedIndex diatur dengan benar, hasilnya akan menjadi indeks yang salah. Namun, jika saya memasukkan instruksi alert() pada waktu yang tepat, instruksi yang benar akan dipilih. Berpikir itu mungkin semacam masalah waktu, saya mencoba sesuatu yang acak yang saya lihat dalam kode sebelumnya:

 var wrapFn = (function() { var myField = field; var myElement = element; return function() { myField.selectedIndex = myElement.index; } })(); setTimeout(wrapFn, 0); 

Dan itu berhasil!

Saya punya solusi untuk masalah saya, tetapi saya malu karena saya tidak tahu persis mengapa ini memperbaiki masalah saya. Adakah yang punya penjelasan resmi? Masalah browser apa yang harus saya hindari dengan memanggil fungsi saya "nanti" menggunakan setTimeout() ?

621
23 апр. diatur oleh Daniel Lew 23 Apr 2009-04-23 00:46 '09 pada 0:46 2009-04-23 00:46
@ 14 jawaban

Ini berfungsi karena Anda melakukan multitasking bersama.

Peramban harus melakukan beberapa hal dengan segera, dan hanya satu di antaranya yang menjalankan JavaScript. Tetapi salah satu hal yang sering digunakan javascript adalah meminta browser untuk membuat elemen tampilan. Sering diasumsikan bahwa ini dilakukan secara serempak (terutama karena JavaScript tidak dieksekusi secara paralel), tetapi tidak ada jaminan bahwa ini yang terjadi, dan JavaScript tidak memiliki mekanisme menunggu yang ditentukan dengan baik.

Solusinya adalah "menjeda" eksekusi JavaScript sehingga rendering threads dapat ditemukan. Dan ini adalah efek yang setTimeout() lakukan dengan batas waktu 0 . Ini mirip dengan hasil utas / proses dalam C. Meskipun sepertinya "segera diluncurkan," itu sebenarnya memberi browser kesempatan untuk menyelesaikan melakukan beberapa hal non-JavaScript yang telah menunggu untuk diselesaikan sebelum memulai bagian baru JavaScript ini.

(Sebenarnya, setTimeout() mengatur u>

IE6 ternyata lebih rentan terhadap kesalahan ini, tetapi saya telah melihatnya terjadi di versi Mozilla yang lebih lama dan di Firefox.

607
23 апр. Jawabannya diberikan oleh staticsan pada 23 April 2009-04-23 03:14 '09 pada 3:14 2009-04-23 03:14

Pendahuluan:

CATATAN PENTING: saat ini paling disetujui dan diterima, jawaban @staticsan yang diterima sebenarnya TIDAK BENAR! - Lihat komentar oleh David Mulder untuk penjelasan alasannya.

Beberapa jawaban lain benar, tetapi tidak benar-benar menggambarkan bahwa masalahnya sedang diselesaikan, jadi saya membuat jawaban ini untuk memberikan ilustrasi terperinci ini.

Karena itu, saya menerbitkan deskripsi terperinci tentang apa yang dilakukan browser dan bagaimana cara menggunakan setTimeout() . Itu terlihat panjang, tetapi sebenarnya sangat sederhana dan jelas - saya baru saja menggambarkannya dengan sangat rinci.

UPDATE: Saya membuat JSFiddle untuk pertunjukan - jelaskan di bawah ini: http://jsfiddle.net/C2YBE/31/ . Banyak terima kasih kepada @ThangChung untuk membantu meluncurkannya.

UPDATE2: Kalau-kalau situs web JSFiddle meninggal atau menghapus kode, saya menambahkan kode untuk jawaban ini di akhir.


RINCIAN

Kirim aplikasi web dengan tombol "lakukan sesuatu" dan hasil div.

Handler onClick untuk tombol "do something" memanggil fungsi "LongCalc ()", yang melakukan 2 hal:

  • Menghitung sangat lama (misalnya, butuh 3 menit)

  • Mencetak hasil perhitungan sebagai hasil dari div.

Sekarang pengguna Anda akan mulai mengujinya, klik tombol "lakukan sesuatu", dan halaman itu duduk di sana tanpa melihat apa pun selama 3 menit, mereka menjadi gelisah, tekan tombol lagi, tunggu 1 menit, tidak ada yang terjadi, tekan tombol lagi ...

Masalahnya jelas - Anda memerlukan "status" DIV, yang menunjukkan apa yang terjadi. Mari kita lihat cara kerjanya.


Jadi, Anda menambahkan status DIV (awalnya kosong) dan mengubah penangan onClick (fungsi LongCalc() ) untuk melakukan 4 hal:

  • Tetapkan status "Perhitungan ... mungkin memerlukan waktu 3 menit" dalam status DIV

  • Menghitung sangat lama (misalnya, butuh 3 menit)

  • Mencetak hasil perhitungan sebagai hasil dari div.

  • Isi status "Perhitungan selesai" di negara bagian DIV

Dan Anda senang untuk mengambil aplikasi untuk menguji u>

Mereka kembali padamu dengan sangat marah. Dan jelaskan bahwa ketika mereka menekan tombol, status DIV tidak pernah diperbarui dengan status "Perhitungan ..." !!!


Anda menggaruk kepala, mengajukan pertanyaan tentang StackOverflow (atau membaca dokumen atau Google) dan memahami masalahnya:

Browser menempatkan semua tugas TODO-nya (tugas antarmuka pengguna dan perintah JavaScript) yang dihasilkan dari peristiwa ke dalam satu antrian . Dan, sayangnya, menggambar kembali "status" DIV dengan nilai baru "Perhitungan ..." adalah TODO terpisah yang menuju akhir antrian!

Berikut ini rincian peristiwa selama pengujian pengguna, konten antrian setelah setiap peristiwa:

  • Antrian: [Empty]
  • Acara: Klik tombol. Antrian setelah acara: [Execute OnClick handler(lines 1-4)]
  • Acara: jalankan baris pertama di penangan OnClick (misalnya, ubah nilai status DIV). Antrian setelah acara: [Execute OnClick handler(lines 2-4), re-draw Status DIV with new "Calculating" value] . Perhatikan bahwa, meskipun perubahan DOM terjadi secara instan, untuk menggambar u> .
  • MASALAH !!! MASALAH !!! Detail dijelaskan di bawah ini.
  • Peristiwa: jalankan baris kedua dalam penangan (perhitungan). Antri setelah: [Execute OnClick handler(lines 3-4), re-draw Status DIV with "Calculating" value] .
  • Peristiwa: jalankan baris ketiga di handler (isikan hasil DIV). Antri setelah: [Execute OnClick handler(line 4), re-draw Status DIV with "Calculating" value, re-draw result DIV with result] .
  • Acara: jalankan baris ke-4 di handler (masukkan status DIV dengan "DONE"). Antrian: [Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value] [Execute OnClick handler, re-draw Status DIV with "Calculating" value, re-draw result DIV with result; re-draw Status DIV with "DONE" value] .
  • Peristiwa: menjalankan return onClick sub handler implisit. Kami mengambil "Execute OnClick handler" dari antrian dan mulai menjalankan item berikutnya dalam antrian.
  • CATATAN Karena kami telah menyelesaikan perhitungan, 3 menit telah berlalu untuk pengguna. Acara menggambar u>
  • Acara: u>
  • Acara: u>
  • Acara: u>SETELAH PERHITUNGAN SELESAI

Jadi, masalah utama adalah bahwa peristiwa-D untuk undian "Status" ditempatkan dalam antrian di akhir, SETELAH acara "mengeksekusi garis 2", yang membutuhkan waktu 3 menit, sehingga undian yang sebenarnya tidak akan terjadi sebelum perhitungan POST.


setTimeout() datang untuk menyelamatkan. Bagaimana ini membantu? Karena, dengan memanggil kode yang dapat dieksekusi panjang melalui setTimeout , Anda sebenarnya membuat 2 peristiwa: eksekusi setTimeout itu sendiri dan (karena batas waktu 0), pisahkan entri antrian untuk kode yang dapat dieksekusi.

Jadi, untuk memperbaiki masalah Anda, Anda mengubah penangan onClick sebagai instruksi DUA (dalam fungsi baru atau hanya di blok di dalam onClick ):

  • Tetapkan status "Perhitungan ... mungkin memerlukan waktu 3 menit" dalam status DIV

  • Jalankan setTimeout() dengan 0 batas waktu dan panggil fungsi LongCalc() .

    LongCalc() hampir sama dengan yang terakhir kali, tetapi, jelas, tidak memiliki status "Perhitungan ..." pembaruan DIV sebagai >

Jadi seperti apa urutan kejadian dan antrian?

  • Antrian: [Empty]
  • Acara: Klik tombol. Antrian setelah acara: [Execute OnClick handler(status update, setTimeout() call)]
  • Acara: jalankan baris pertama di penangan OnClick (misalnya, ubah nilai status DIV). Antrian setelah acara: [Execute OnClick handler(which is a setTimeout call), re-draw Status DIV with new "Calculating" value] .
  • Acara: jalankan baris kedua di handler (panggil setTimeout). Antrian setelah: [re-draw Status DIV with "Calculating" value] . Tidak ada yang baru dalam antrian selama 0 detik.
  • Acara: Alarm batas waktu mati, setelah 0 detik. Antri setelah: [re-draw Status DIV with "Calculating" value, execute LongCalc (lines 1-3)] .
  • Peristiwa: redraw status DIV dengan nilai "Dihitung" . Antrian setelah: [execute LongCalc (lines 1-3)] . Harap dicatat bahwa acara menggambar u>
  • ...

Hore! Status DIV baru saja diperbarui ke "Perhitungan ..." sebelum memulai perhitungan !!!



Di bawah ini adalah kode contoh dari JSFiddle yang menggambarkan contoh-contoh ini: http://jsfiddle.net/C2YBE/31/ :

Kode HTML:

 <table border=1> <tr><td><button id='do'>Do long calc - bad status!</button></td> <td><div id='status'>Not Calculating yet.</div></td> </tr> <tr><td><button id='do_ok'>Do long calc - good status!</button></td> <td><div id='status_ok'>Not Calculating yet.</div></td> </tr> </table> 

Kode JavaScript: (Dapat dijalankan di onDomReady dan jQuery 1.9 mungkin diperlukan)

 function long_running(status_div) { var result = 0; // Use 1000/700/300 limits in Chrome, // 300/100/100 in IE8, // 1000/500/200 in FireFox // I have no idea why identical runtimes fail on diff browsers. for (var i = 0; i < 1000; i++) { for (var j = 0; j < 700; j++) { for (var k = 0; k < 300; k++) { result = result + i + j + k; } } } $(status_div).text('calculation done'); } // Assign events to buttons $('#do').on('click', function () { $('#status').text('calculating....'); long_running('#status'); }); $('#do_ok').on('click', function () { $('#status_ok').text('calculating....'); // This works on IE8. Works in Chrome // Does NOT work in FireFox 25 with timeout =0 or =1 // DOES work in FF if you change timeout from 0 to 500 window.setTimeout(function (){ long_running('#status_ok') }, 0); }); 
 function long_running(status_div) { var result = 0; // Use 1000/700/300 limits in Chrome, // 300/100/100 in IE8, // 1000/500/200 in FireFox // I have no idea why identical runtimes fail on diff browsers. for (var i = 0; i < 1000; i++) { for (var j = 0; j < 700; j++) { for (var k = 0; k < 300; k++) { result = result + i + j + k; } } } $(status_div).text('calculation done'); } // Assign events to buttons $('#do').on('click', function () { $('#status').text('calculating....'); long_running('#status'); }); $('#do_ok').on('click', function () { $('#status_ok').text('calculating....'); // This works on IE8. Works in Chrome // Does NOT work in FireFox 25 with timeout =0 or =1 // DOES work in FF if you change timeout from 0 to 500 window.setTimeout(function (){ long_running('#status_ok') }, 0); }); 
492
01 янв. jawabannya diberikan DVK 01 Jan. 2011-01-01 20:53 '11 pada 20:53 2011-01-01 20:53

Lihatlah artikel John Resig tentang Cara Kerja Pengatur Waktu JavaScript . Ketika Anda mengatur batas waktu, itu benar-benar mengantri kode asinkron sampai menjalankan tumpukan panggilan saat ini.

74
07 дек. Balas Andy 07 Des 2010-12-07 00:38 '10 pada 0:38 2010-12-07 00:38

Sebagian besar browser memiliki proses yang disebut utas utama, yang bertanggung jawab untuk melakukan beberapa tugas JavaScript, memperbarui antarmuka pengguna, misalnya menggambar, menggambar u>

Beberapa tugas memperbarui JavaScript dan mengeksekusi antarmuka pengguna antri dalam antrian pesan browser, kemudian dikirim ke utas utama browser yang akan dieksekusi.

Saat pembaruan antarmuka pengguna dibuat, saat utas utama sibuk, tugas ditambahkan ke antrian pesan.

setTimeout(fn, 0); tambahkan fn ini ke akhir antrian untuk dieksekusi. Dia berencana untuk menambahkan tugas ke antrian pesan setelah periode waktu tertentu.

21
26 февр. jawabannya diberikan pada 26 Februari. 2013-02-26 00:27 '13 pada 0:27 2013-02-26 00:27

setTimeout() Anda membeli beberapa waktu hingga elemen DOM dimuat, bahkan jika diatur ke 0.

Lihat ini: setTimeout

18
23 апр. Jawab Jose Basilio 23 Apr 2009-04-23 00:52 '09 pada 0:52 2009-04-23 00:52

Ada jawaban kontradiktif di sini, dan tanpa bukti tidak ada cara untuk mengetahui siapa yang harus dipercaya. Inilah bukti bahwa @DVK benar dan @SalvadorDali salah. Negara bagian terakhir:

"Dan inilah sebabnya: tidak mungkin untuk mengatur setTimeout dengan penundaan 0 milidetik. Nilai minimum ditentukan oleh browser, dan itu bukan 0 milidetik. Secara historis, browser menetapkan ini setidaknya 10 milidetik, tetapi spesifikasi HTML5 dan browser modern menetapkannya menjadi 4 milidetik."

Batas waktu minimum 4 ms tidak relevan dengan apa yang terjadi. Apa yang sebenarnya terjadi adalah setTimeout mendorong fungsi callback ke akhir antrian eksekusi. Jika setelah setTimeout (panggilan balik, 0) Anda memiliki kode kunci yang memerlukan waktu beberapa detik, maka panggilan balik tidak akan dieksekusi selama beberapa detik hingga kode kunci selesai. Coba kode ini:

 function testSettimeout0 () { var startTime = new Date().getTime() console.log('setting timeout 0 callback at ' +sinceStart()) setTimeout(function(){ console.log('in timeout callback at ' +sinceStart()) }, 0) console.log('starting blocking loop at ' +sinceStart()) while (sinceStart() < 3000) { continue } console.log('blocking loop ended at ' +sinceStart()) return // functions below function sinceStart () { return new Date().getTime() - startTime } // sinceStart } // testSettimeout0 

Keluar:

 setting timeout 0 callback at 0 starting blocking loop at 5 blocking loop ended at 3000 in timeout callback at 3033 
15
26 июля '14 в 3:30 2014-07-26 03:30 Jawaban diberikan oleh Vladimir Kornea pada 26 Juli '14 pada 3:30 2014-07-26 03:30

Salah satu alasan untuk ini adalah untuk menunda pelaksanaan kode sampai siklus terpisah dari peristiwa berikutnya. Saat menanggapi acara browser (misalnya, klik mouse), terkadang perlu untuk melakukan operasi hanya setelah memproses acara saat ini. Objek setTimeout() adalah cara termudah untuk melakukan ini.

sekarang edit bahwa pada tahun 2015 saya harus mencatat bahwa ada juga requestAnimationFrame() , yang tidak persis sama, tetapi cukup dekat dengan setTimeout(fn, 0) , yang layak disebut.

12
01 янв. jawabannya diberikan Pointy Jan 01 2011-01-01 20:38 '11 pada 20:38 2011-01-01 20:38

Ini adalah pertanyaan lama dengan jawaban lama. Saya ingin menambahkan tampilan baru pada masalah ini dan menjawab mengapa itu terjadi, dan bukan mengapa itu berguna.

Jadi, Anda memiliki dua fungsi:

 var f1 = function () { setTimeout(function(){ console.log("f1", "First function call..."); }, 0); }; var f2 = function () { console.log("f2", "Second call..."); }; 

dan kemudian memanggil mereka dengan urutan berikut f1(); f2(); f1(); f2(); untuk melihat bahwa yang kedua berjalan terlebih dahulu.

Dan inilah sebabnya: tidak mungkin memiliki setTimeout dengan penundaan 0 milidetik. Nilai minimum ditentukan oleh browser , dan tidak sama dengan 0 milidetik. Secara historis, browser menetapkan minimum ini hingga 10 milidetik, tetapi spesifikasi HTML5 dan browser modern menetapkannya menjadi 4 milidetik,

Jika level bersarang lebih dari 5, dan batas waktu kurang dari 4, maka tambah batas waktu menjadi 4.

Juga dari mozilla:

Untuk mewujudkan batas waktu 0 ms dalam peramban modern, Anda dapat menggunakan window.postMessage (), seperti dijelaskan di sini .

Informasi PS diambil setelah membaca artikel berikutnya.

9
20 мая '14 в 0:34 2014-05-20 00:34 jawabannya diberikan oleh Salvador Dali 20 Mei '14 pukul 0:34 2014-05-20 00:34

Karena telah melewati panjang 0 , saya percaya bahwa perlu untuk menghapus kode yang diteruskan ke setTimeout dari utas. Oleh karena itu, jika ini adalah fungsi yang mungkin memerlukan waktu, itu tidak akan mencegah eksekusi kode berikut.

8
01 янв. jawaban yang diberikan oleh user113716 01 Jan 2011-01-01 20:39 '11 pada 20:39 2011-01-01 20:39

Hal lain adalah dengan mendorong panggilan fungsi di bagian bawah tumpukan, mencegah tumpukan meluap jika Anda memanggil fungsi secara rekursif. Ini mengarah ke while , tetapi memungkinkan mekanisme javascript untuk memulai timer asinkron lainnya.

4
21 июня '12 в 4:14 2012-06-21 04:14 Jawaban diberikan oleh Jason Suárez pada 21 Juni '12 pada 4:14 am 2012-06-21 04:14

Respons untuk mengeksekusi loop dan rendering DOM sebelum mengeksekusi beberapa kode lain sudah benar. Nol timeout sekunder dalam JavaScript membantu membuat kode pseudo-multi-threaded, meskipun hal ini tidak terjadi.

Saya ingin menambahkan bahwa nilai TERBAIK untuk latensi lintas browser / lintas platform nol dalam JavaScript sebenarnya sekitar 20 milidetik alih-alih 0 (nol), karena banyak peramban seluler tidak dapat mencatat batas waktu yang kurang dari 20 milidetik untuk membatasi jam pada chip AMD.

Selain itu, proses yang telah berjalan lama yang tidak terkait dengan manipulasi DOM sekarang harus dikirim ke pekerja web, karena mereka memberikan eksekusi benar JavaScript multi-threaded.

2
14 марта '13 в 2:29 2013-03-14 02:29 jawabannya diberikan oleh ChrisN pada 14 Maret 2013 di 2:29 2013-03-14 02:29

Dengan memanggil setTimeout, Anda menentukan waktu halaman untuk menanggapi apa yang dilakukan pengguna. Ini sangat berguna untuk fungsi yang dilakukan selama pemuatan halaman.

1
23 апр. Balas diberikan oleh Jeremy pada 23 April 2009-04-23 00:53 '09 pada 0:53 2009-04-23 00:53

Beberapa kasus lain di mana setTimeout berguna:

Anda ingin memutus siklus yang panjang atau menghitungnya menjadi komponen yang lebih kecil sehingga browser tidak terlihat "beku" atau mengatakan bahwa "Script pada halaman sedang sibuk."

Anda ingin menonaktifkan tombol kirim ketika Anda mengklik, tetapi jika Anda menonaktifkan tombol di penangan onClick, formulir tidak akan dikirimkan. SetTimeout bernilai nol melakukan triknya, memungkinkan acara untuk berakhir, formulir untuk mulai mengirim, maka tombol Anda dapat dinonaktifkan.

1
14 дек. jawabannya diberikan fabspro 14 Desember. 2012-12-14 16:09 '12 pada pukul 4:09 PM 2012-12-14 16:09

setTimout pada 0 juga sangat berguna dalam templat pengaturan janji menunggu yang ingin Anda kembalikan segera:

 myObject.prototype.myMethodDeferred = function() { var deferredObject = $.Deferred(); var that = this; // Because setTimeout won't work right with this setTimeout(function() { return myMethodActualWork.call(that, deferredObject); }, 0); return deferredObject.promise(); } 
1
02 сент. Jawabannya diberikan oleh Stephan G 02 Sep. 2015-09-02 18:39 '15 pada 18:39 2015-09-02 18:39

Pertanyaan lain tentang tag atau Ajukan Pertanyaan