Memandangkan contoh berikut, mengapaouterScopeVar
tidak ditentukan dalam semua kes?
var outerScopeVar; var img = document.createElement('img'); img.onload = function() { outerScopeVar = this.width; }; img.src = 'lolcat.png'; alert(outerScopeVar);
var outerScopeVar; setTimeout(function() { outerScopeVar = 'Hello Asynchronous World!'; }, 0); alert(outerScopeVar);
// Example using some jQuery var outerScopeVar; $.post('loldog', function(response) { outerScopeVar = response; }); alert(outerScopeVar);
// Node.js example var outerScopeVar; fs.readFile('./catdog.html', function(err, data) { outerScopeVar = data; }); console.log(outerScopeVar);
// with promises var outerScopeVar; myPromise.then(function (response) { outerScopeVar = response; }); console.log(outerScopeVar);
// with observables var outerScopeVar; myObservable.subscribe(function (value) { outerScopeVar = value; }); console.log(outerScopeVar);
// geolocation API var outerScopeVar; navigator.geolocation.getCurrentPosition(function (pos) { outerScopeVar = pos; }); console.log(outerScopeVar);
Mengapaundefined
keluaran dalam semua contoh ini? Saya tidak perlukan penyelesaian, saya ingin tahukenapaini berlaku.
Nota:Ini ialah soalan spesifikasi tentang ketidaksegerakan JavaScript. Sila berasa bebas untuk menambah baik soalan ini dan menambah contoh yang lebih mudah yang boleh dipersetujui oleh komuniti.
Jawapan Fabrício sangat betul; tetapi saya ingin menambah jawapannya dengan sesuatu yang kurang teknikal, memfokuskan untuk membantu menjelaskan konsep asynchronicity melalui analogi.
Analogi...
Semalam, saya perlu mendapatkan beberapa maklumat daripada rakan sekerja saya untuk kerja yang saya lakukan. Saya memanggilnya; perbualannya seperti ini:
Pada ketika ini, saya meletakkan telefon. Memandangkan saya memerlukan maklumat daripada Bob untuk melengkapkan laporan saya, saya meninggalkan laporan itu, pergi untuk mendapatkan secawan kopi, dan kemudian saya membaca beberapa e-mel. 40 minit kemudian (Bob lambat) Bob menelefon semula dan memberi saya maklumat yang saya perlukan. Pada ketika ini, saya terus membuat laporan saya kerana saya mempunyai semua maklumat yang saya perlukan.
Bayangkan kalau perbualan jadi begini;
Saya duduk di sana dan menunggu. dan menunggu. dan menunggu. 40 minit. Buat apa-apa kecuali tunggu. Akhirnya, Bob memberi saya maklumat, kami menutup telefon, dan saya menyelesaikan laporan saya. Tetapi saya kehilangan produktiviti selama 40 minit.
Ini ialah tingkah laku tak segerak vs segerak
Inilah yang berlaku dalam semua contoh dalam soalan kami. Memuatkan imej, memuatkan fail dari cakera, dan meminta halaman melalui AJAX semuanya adalah operasi yang perlahan (dalam konteks pengkomputeran moden).
JavaScript membolehkan anda mendaftarkan fungsi panggil balik yang akan dilaksanakan apabila operasi perlahan selesai, bukannyamenungguuntuk operasi perlahan ini selesai. Tetapi dalam masa yang sama, JavaScript akan terus melaksanakan kod lain. Hakikat bahawa JavaScript melaksanakankod lainsementara menunggu operasi perlahan selesai menjadikan tingkah lakutak segerak. Jika JavaScript menunggu operasi selesai sebelum melaksanakan sebarang kod lain, ini akan menjadi gelagatsegerak.
Dalam kod di atas, kami meminta JavaScript untuk memuatkan
. Setelah operasi perlahan ini selesai, fungsi panggil balik akan dilaksanakan, tetapi dalam masa yang sama, JavaScript akan terus memproses baris kod seterusnya iaitu,lolcat.png
, yang merupakan operasilolcat.png
,这是一个sloooow操作。一旦这个缓慢的操作完成,回调函数就会被执行,但与此同时,JavaScript 将继续处理下一行代码;即alert(outerScopeVar)
sloooowalert(outerScopeVar)
.
diproses serta-merta, bukan selepas imej dimuatkan.未定义
;因为alert()
Itulah sebabnya kami melihat amaran menunjukkan
sebagai pembolehubah global. Anda akansentiasamelihat panggilan balik yang ditentukan sebagai fungsi kerana itu satu-satunya cara dalam JavaScript untuk mentakrifkan beberapa kod tetapi kemudian melaksanakannya kemudian.alert(outerScopeVar)
代码移到回调函数中。因此,我们不再需要将outerScopeVar
Untuk membetulkan kod kami, yang perlu kami lakukan ialah mengisytiharkan pembolehubah
contoh, yang perlu kita lakukan ialah mengalihkan kod yang memerlukan tindak balas tindakan di sana!function() { /* Do Something */ }
Jadi, dalam semua contoh kami,ialah panggilan balik; untuk membetulkansemua* Secara teknikal anda juga boleh menggunakan
eval()
,但是eval()
kejahatan untuk tujuan iniBagaimana untuk menahan pemanggil?
Anda mungkin mempunyai beberapa kod yang serupa dengan ini;
Tetapi, kita tahu sekarang
返回outerScopeVar
会立即发生;在onload
回调函数更新变量之前。这会导致getWidthOfImage()
返回undefined
,并发出警报undefined
.Untuk membetulkannya, kita perlu membenarkan fungsi memanggil
getWidthOfImage()
untuk mendaftarkan panggilan balik dan kemudian mengalihkan amaran lebar di dalam panggilan balik itu...Seperti sebelum ini, ambil perhatian bahawa kami telah dapat mengalih keluar pembolehubah global (dalam kes ini
width
).Jawapan satu perkataan:Asynchronicity.
Kata Pengantar
Topik ini telah diulang sekurang-kurangnya beribu kali pada Stack Overflow. Jadi pertama saya ingin menunjukkan beberapa sumber yang sangat berguna:
@Jawapan Felix Kling kepada "Bagaimana untuk membalas respons daripada panggilan tak segerak?". Lihat jawapan cemerlang beliau yang menerangkan proses segerak dan tak segerak, serta bahagian "Menyusun semula kod anda".
@Benjamin Gruenbaum juga meletakkan banyak usaha untuk menerangkan ketaksinkronan dalam urutan yang sama.
Jawapan @Matt Esch untuk "Mendapatkan data daripada fs.readFile"juga menerangkan ketidaksegerakan dengan cara yang mudah dengan baik.
Jawapan kepada soalan semasa
Mari kita jejak tingkah laku biasa dahulu. Dalam semua contoh,
outerScopeVar
diubah suai di dalam fungsi. Fungsi ini jelas tidak dilaksanakan serta-merta; ia ditetapkan atau diluluskan sebagai parameter. Inilah yang kami panggilpanggilan balik.Sekarang persoalannya, bila panggilan balik ini dipanggil?
Ia bergantung pada situasi tertentu. Mari cuba jejak beberapa gelagat biasa sekali lagi:
img.onload
Mungkin dipanggilpada masa akan datang(jika) imej telah berjaya dimuatkan.setTimeout
boleh dipanggilsetTimeout
可能会在延迟到期且超时尚未被clearTimeout
取消后在将来的某个时间被调用。注意:即使使用0
suatu masa pada masa hadapanclearTimeout
. Nota: Walaupun semasa menggunakan0
sebagai kelewatan, semua penyemak imbas mempunyai had atas pada kelewatan tamat masa minimum (dinyatakan sebagai 4 milisaat dalam spesifikasi HTML5).$.post
panggilan balik jQueryfs.readFile
Node.js'boleh dipanggilpada masa hadapanDalam semua kes, kami mempunyai panggilan balik yang mungkin dijalankankadang-kadangpada masa hadapan. "Kadang-kadang pada masa hadapan" inilah yang kami panggilaliran tak segerak
.Pelaksanaan tak segerak ditolak keluar daripada proses segerak. Iaitu, semasa tindanan kod segerak sedang dilaksanakan, kod tak segerak akanselamanya
dilaksanakan. Inilah yang JavaScript adalah satu utas tentang.Lebih khusus lagi, apabila enjin JS melahu - tidak melaksanakan sekumpulan (a)kod segerak - ia akan meninjau peristiwa yang mungkin mencetuskan panggilan balik tak segerak (cth. tamat masa, respons rangkaian diterima) dan melaksanakannya satu demi satu. Ini dianggap sebagaigelung peristiwa
. Iaitu, kod tak segerak yang diserlahkan dalam bentuk merah lukisan tangan hanya boleh dilaksanakan selepas semua kod segerak yang tinggal dalam blok kod masing-masing telah dilaksanakan: Ringkasnya, fungsi panggil balik dicipta secara serentak tetapi dilaksanakan secara tidak segerak. Anda tidak boleh bergantung pada pelaksanaan fungsi async sehingga anda tahu ia telah dilaksanakan, bagaimana anda melakukan ini?Sangat mudah. Logik yang bergantung pada pelaksanaan fungsi async harus dimulakan/dipanggil dari dalam fungsi async tersebut. Contohnya, memindahkan
alert
和console.log
di dalam fungsi panggil balik akan mengeluarkan hasil yang dijangkakan kerana hasilnya tersedia pada masa itu.Laksanakan logik panggil balik anda sendiri
Selalunya anda perlu melakukan lebih banyak operasi pada hasil fungsi async, atau melakukan operasi yang berbeza pada hasil bergantung pada tempat fungsi async dipanggil. Mari kita berurusan dengan contoh yang lebih kompleks:
Nota:Saya menggunakan
setTimeout
dengan kelewatan rawak sebagai fungsi async generik; contoh yang sama berfungsi untuk Ajax, readFile, onload dan sebarang aliran tak segerak yang lain.Contoh ini nampaknya mempunyai masalah yang sama seperti contoh lain; ia tidak menunggu sehingga fungsi async dilaksanakan.
Mari selesaikan masalah ini dengan melaksanakan sistem panggilan balik kami sendiri. Mula-mula, kita hapuskan
outerScopeVar
yang hodoh itu, yang sama sekali tidak berguna dalam kes ini. Kemudian kami menambah parameter yang menerima hujah fungsi, panggilan balik kami. Apabila operasi tak segerak selesai, kami memanggil panggilan balik ini dan lulus hasilnya. Perlaksanaan (sila baca komen mengikut urutan):Coretan kod untuk contoh di atas: