Dengan contoh-contoh berikut, mengapa tidak outerScopeVar
terdefinisi dalam semua kasus?
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);
// geolocation API
var outerScopeVar;
navigator.geolocation.getCurrentPosition(function (pos) {
outerScopeVar = pos;
});
console.log(outerScopeVar);
Mengapa hasilnya undefined
dalam semua contoh ini? Saya tidak ingin penyelesaian, saya ingin tahu mengapa ini terjadi.
Catatan: Ini adalah pertanyaan kanonik untuk JavaScript asynchronicity . Jangan ragu untuk memperbaiki pertanyaan ini dan menambahkan lebih banyak contoh sederhana yang dapat diidentifikasi oleh komunitas.
javascript
asynchronous
Fabrício Matté
sumber
sumber
Jawaban:
Satu kata jawaban: asynchronicity .
Kata pengantar
Topik ini telah diulang setidaknya beberapa ribu kali, di sini, di Stack Overflow. Karenanya, pertama saya ingin menunjukkan beberapa sumber daya yang sangat berguna:
@Felix Kling menjawab "Bagaimana cara mengembalikan respons dari panggilan asinkron?" . Lihat jawabannya yang sangat bagus menjelaskan aliran sinkron dan asinkron, serta bagian "Kode restrukturisasi".
@Benjamin Gruenbaum juga berupaya menjelaskan asinkronisitas di utas yang sama.
@Matt Esch jawaban untuk "Dapatkan data dari fs.readFile" juga menjelaskan asynchronicity dengan sangat baik dengan cara yang sederhana.
Jawaban atas pertanyaan yang ada
Mari kita telusuri perilaku umum dulu. Dalam semua contoh,
outerScopeVar
dimodifikasi di dalam suatu fungsi . Fungsi itu jelas tidak segera dieksekusi, sedang ditugaskan atau diteruskan sebagai argumen. Itulah yang kami sebut panggilan balik .Sekarang pertanyaannya adalah, kapan panggilan balik itu dipanggil?
Tergantung kasusnya. Mari kita coba melacak beberapa perilaku umum lagi:
img.onload
dapat dipanggil suatu saat nanti , ketika (dan jika) gambar telah berhasil dimuat.setTimeout
dapat dipanggil kapan - kapan di masa depan , setelah penundaan berakhir dan batas waktu belum dibatalkan olehclearTimeout
. Catatan: bahkan ketika menggunakan0
sebagai penundaan, semua browser memiliki batas waktu tunggu batas waktu minimum (ditetapkan menjadi 4ms dalam spesifikasi HTML5).$.post
balik jQuery dapat dipanggil kapan saja di masa mendatang , ketika (dan jika) permintaan Ajax telah berhasil diselesaikan.fs.readFile
mungkin dipanggil di masa mendatang , ketika file telah dibaca dengan sukses atau dilempar kesalahan.Dalam semua kasus, kami memiliki panggilan balik yang dapat berjalan di masa mendatang . "Suatu saat di masa depan" inilah yang kami sebut sebagai aliran asinkron .
Eksekusi asinkron didorong keluar dari aliran sinkron. Yaitu, kode asinkron tidak akan pernah dijalankan ketika tumpukan kode sinkron dijalankan. Ini adalah arti JavaScript menjadi single-threaded.
Lebih khusus lagi, ketika mesin JS idle - tidak mengeksekusi tumpukan (a) kode sinkron - itu akan polling untuk peristiwa yang mungkin telah memicu callback tidak sinkron (misalnya batas waktu habis, respon jaringan yang diterima) dan menjalankannya satu demi satu. Ini dianggap sebagai Perulangan Acara .
Yaitu, kode asinkron yang disorot dalam bentuk merah digambar tangan hanya dapat dijalankan setelah semua kode sinkron yang tersisa di masing-masing blok kode telah dieksekusi:
Singkatnya, fungsi-fungsi panggilan balik dibuat secara sinkron tetapi dijalankan secara tidak sinkron. Anda tidak bisa mengandalkan eksekusi fungsi asinkron sampai Anda tahu itu telah dieksekusi, dan bagaimana melakukannya?
Sederhana, sungguh. Logika yang bergantung pada eksekusi fungsi asinkron harus dimulai / dipanggil dari dalam fungsi asinkron ini. Misalnya, memindahkan
alert
s danconsole.log
s terlalu dalam fungsi panggilan balik akan menampilkan hasil yang diharapkan, karena hasilnya tersedia pada saat itu.Menerapkan logika panggilan balik Anda sendiri
Seringkali Anda perlu melakukan lebih banyak hal dengan hasil dari fungsi asinkron atau melakukan hal yang berbeda dengan hasil tergantung di mana fungsi asinkron dipanggil. Mari kita bahas contoh yang sedikit lebih rumit:
Catatan: Saya menggunakan
setTimeout
dengan penundaan acak sebagai fungsi asinkron generik, contoh yang sama berlaku untuk AjaxreadFile
,,onload
dan aliran asinkron lainnya.Contoh ini jelas menderita dari masalah yang sama seperti contoh lainnya, tidak menunggu sampai fungsi asinkron dijalankan.
Mari kita hadapi itu menerapkan sistem panggilan balik kita sendiri. Pertama, kita menyingkirkan yang jelek
outerScopeVar
yang sama sekali tidak berguna dalam kasus ini. Kemudian kami menambahkan parameter yang menerima argumen fungsi, panggilan balik kami. Ketika operasi asinkron selesai, kami memanggil panggilan balik ini melewati hasilnya. Implementasinya (harap baca komentar secara berurutan):Cuplikan kode dari contoh di atas:
Paling sering dalam kasus penggunaan nyata, DOM API dan sebagian besar perpustakaan sudah menyediakan fungsi panggilan balik (
helloCatAsync
implementasi dalam contoh demonstratif ini). Anda hanya perlu melewati fungsi callback dan memahami bahwa itu akan mengeksekusi keluar dari aliran sinkron, dan menyusun kembali kode Anda untuk mengakomodasi itu.Anda juga akan melihat bahwa karena sifat asinkron, tidak mungkin
return
nilai dari aliran asinkron kembali ke aliran sinkron di mana panggilan balik ditentukan, karena panggilan balik asinkron dijalankan lama setelah kode sinkron telah selesai dijalankan.Alih-alih
return
memasukkan nilai dari callback asinkron, Anda harus menggunakan pola callback, atau ... Janji.Janji
Meskipun ada cara untuk menjaga panggilan balik dengan vanilla JS, janji semakin populer dan saat ini sedang distandarisasi dalam ES6 (lihat Janji - MDN ).
Promises (alias Futures) memberikan pembacaan kode asinkron yang lebih linier, dan dengan demikian menyenangkan, tetapi menjelaskan seluruh fungsionalitasnya berada di luar cakupan pertanyaan ini. Sebagai gantinya, saya akan meninggalkan sumber daya yang bagus ini untuk yang tertarik:
Lebih banyak bahan bacaan tentang JavaScript asynchronicity
sumber
Jawaban Fabrício sangat tepat; tetapi saya ingin melengkapi jawabannya dengan sesuatu yang kurang teknis, yang berfokus pada analogi untuk membantu menjelaskan konsep asinkronisitas .
Analogi ...
Kemarin, pekerjaan yang saya lakukan memerlukan beberapa informasi dari seorang kolega. Saya meneleponnya; Beginilah percakapan berlangsung:
Pada titik ini, saya menutup telepon. Karena saya memerlukan informasi dari Bob untuk menyelesaikan laporan saya, saya meninggalkan laporan dan pergi untuk minum kopi, kemudian saya menyusul beberapa email. 40 menit kemudian (Bob lambat), Bob menelepon kembali dan memberi saya informasi yang saya butuhkan. Pada titik ini, saya melanjutkan pekerjaan saya dengan laporan saya, karena saya memiliki semua informasi yang saya butuhkan.
Bayangkan jika percakapan sudah seperti ini sebagai gantinya;
Dan saya duduk di sana dan menunggu. Dan menunggu. Dan menunggu. Selama 40 menit. Tidak melakukan apa-apa selain menunggu. Akhirnya, Bob memberi saya informasi, kami menutup telepon, dan saya menyelesaikan laporan saya. Tapi saya kehilangan 40 menit produktivitas.
Ini adalah perilaku asinkron vs sinkron
Inilah yang terjadi dalam semua contoh dalam pertanyaan kami. Memuat gambar, memuat file dari disk, dan meminta halaman melalui AJAX semuanya berjalan lambat (dalam konteks komputasi modern).
Daripada menunggu operasi lambat ini selesai, JavaScript memungkinkan Anda mendaftarkan fungsi panggilan balik yang akan dieksekusi ketika operasi lambat selesai. Sementara itu, JavaScript akan terus mengeksekusi kode lain. Fakta bahwa JavaScript mengeksekusi kode lain sambil menunggu operasi lambat untuk menyelesaikan membuat perilaku tidak sinkron . Seandainya JavaScript menunggu operasi untuk diselesaikan sebelum mengeksekusi kode lain, ini akan menjadi perilaku sinkron .
Pada kode di atas, kami meminta JavaScript untuk memuat
lolcat.png
, yang merupakan operasi sloooow . Fungsi panggilan balik akan dieksekusi setelah operasi lambat ini selesai, tetapi sementara itu, JavaScript akan terus memproses baris kode berikutnya; yaitualert(outerScopeVar)
.Inilah sebabnya kami melihat lansiran ditampilkan
undefined
; sejakalert()
diproses segera, bukan setelah gambar telah dimuat.Untuk memperbaiki kode kita, yang harus kita lakukan adalah memindahkan
alert(outerScopeVar)
kode ke fungsi panggilan balik. Sebagai konsekuensi dari ini, kita tidak perlu lagiouterScopeVar
variabel dideklarasikan sebagai variabel global.Anda akan selalu melihat panggilan balik ditentukan sebagai fungsi, karena itulah satu-satunya * cara dalam JavaScript untuk mendefinisikan beberapa kode, tetapi tidak menjalankannya sampai nanti.
Karena itu, dalam semua contoh kami,
function() { /* Do something */ }
ini adalah panggilan baliknya; untuk memperbaiki semua contoh, yang harus kita lakukan adalah memindahkan kode yang memerlukan respons operasi ke sana!* Secara teknis Anda dapat menggunakan
eval()
juga, tetapieval()
jahat untuk tujuan iniBagaimana cara membuat penelepon saya menunggu?
Saat ini Anda mungkin memiliki beberapa kode yang mirip dengan ini;
Namun, kita sekarang tahu bahwa itu
return outerScopeVar
terjadi segera; sebelumonload
fungsi callback memperbarui variabel. Ini mengarah kegetWidthOfImage()
pengembalianundefined
, danundefined
diperingatkan.Untuk memperbaikinya, kita perlu mengizinkan fungsi panggilan
getWidthOfImage()
untuk mendaftarkan panggilan balik, lalu memindahkan peringatan lebar menjadi dalam panggilan balik itu;... seperti sebelumnya, perhatikan bahwa kami dapat menghapus variabel global (dalam hal ini
width
).sumber
Berikut ini jawaban yang lebih ringkas untuk orang yang mencari referensi cepat serta beberapa contoh menggunakan janji dan asink / tunggu.
Mulai dengan pendekatan naif (yang tidak berfungsi) untuk fungsi yang memanggil metode asinkron (dalam kasus ini
setTimeout
) dan mengembalikan pesan:undefined
masuk dalam kasus ini karenagetMessage
kembali sebelumsetTimeout
panggilan balik dipanggil dan diperbaruiouterScopeVar
.Dua cara utama untuk menyelesaikannya adalah menggunakan callback dan janji :
Telepon balik
Perubahan di sini adalah
getMessage
menerimacallback
parameter yang akan dipanggil untuk mengirimkan hasilnya kembali ke kode panggilan yang pernah tersedia.Janji
Janji memberikan alternatif yang lebih fleksibel daripada callback karena mereka dapat secara alami digabungkan untuk mengoordinasikan beberapa operasi async. Sebuah Janji / A + penerapan standar native disediakan dalam node.js (0.12+) dan banyak browser saat ini, tetapi juga diterapkan di perpustakaan seperti Bluebird dan Q .
jQuery Deferreds
jQuery menyediakan fungsionalitas yang mirip dengan janji dengan Tangguhannya.
async / tunggu
Jika lingkungan JavaScript Anda menyertakan dukungan untuk
async
danawait
(seperti Node.js 7.6+), maka Anda dapat menggunakan janji secara sinkron dalamasync
fungsi:sumber
function getMessage(param1, param2, callback) {...}
.async/await
sampel Anda , tetapi saya mengalami masalah. Alih-alih membuat contohnew Promise
, saya membuat.Get()
panggilan dan karena itu tidak memiliki akses keresolve()
metode apa pun . Jadi sayagetMessage()
mengembalikan Janji dan bukan hasilnya. Bisakah Anda mengedit jawaban Anda sedikit untuk menunjukkan sintaks yang berfungsi untuk ini?.Get()
panggilan. Mungkin yang terbaik untuk mengirim pertanyaan baru.Untuk menyatakan yang jelas, cawan mewakili
outerScopeVar
.Fungsi asinkron menjadi seperti ...
sumber
Jawaban lainnya sangat bagus dan saya hanya ingin memberikan jawaban langsung untuk ini. Hanya membatasi untuk panggilan sinkron jQuery jQuery
Semua panggilan ajax (termasuk
$.get
atau$.post
atau$.ajax
) tidak sinkron.Mempertimbangkan contoh Anda
Eksekusi kode dimulai dari baris 1, mendeklarasikan variabel dan pemicu serta panggilan tidak sinkron di jalur 2, (yaitu, permintaan pos) dan melanjutkan eksekusi dari baris 3, tanpa menunggu permintaan pos untuk menyelesaikan pelaksanaannya.
Katakanlah bahwa permintaan posting selesai 10 detik, nilai
outerScopeVar
hanya akan ditetapkan setelah 10 detik itu.Untuk mencoba,
Sekarang ketika Anda menjalankan ini, Anda akan mendapatkan peringatan di jalur 3. Sekarang tunggu beberapa saat sampai Anda yakin permintaan pos telah mengembalikan beberapa nilai. Kemudian ketika Anda mengklik OK, pada kotak peringatan, peringatan berikutnya akan mencetak nilai yang diharapkan, karena Anda menunggu.
Dalam skenario kehidupan nyata, kode tersebut menjadi,
Semua kode yang bergantung pada panggilan asinkron, dipindahkan di dalam blok asinkron, atau dengan menunggu panggilan asinkron.
sumber
or by waiting on the asynchronous calls
Bagaimana seseorang melakukan itu?Dalam semua skenario
outerScopeVar
ini dimodifikasi atau diberikan nilai secara tidak sinkron atau terjadi di kemudian hari (menunggu atau mendengarkan beberapa peristiwa terjadi), di mana eksekusi saat ini tidak akan menunggu . Jadi semua kasus ini arus eksekusi saat ini menghasilkanouterScopeVar = undefined
Mari kita bahas masing-masing contoh (saya menandai bagian yang disebut tidak sinkron atau tertunda agar beberapa peristiwa terjadi):
1.
Di sini kita mendaftarkan eventlistner yang akan dieksekusi pada peristiwa tertentu. Di sini memuat gambar. Kemudian eksekusi saat ini berlanjut dengan baris berikutnya
img.src = 'lolcat.png';
danalert(outerScopeVar);
sementara itu acara mungkin tidak terjadi. yaitu, funtionimg.onload
menunggu gambar yang dimaksud untuk dimuat, tanpa sinkronisasi. Ini akan terjadi semua contoh berikut - acara mungkin berbeda.2.
Di sini acara timeout memainkan peran, yang akan memanggil pawang setelah waktu yang ditentukan. Ini dia
0
, tapi tetap saja mendaftarkan peristiwa asinkron itu akan ditambahkan ke posisi terakhir dariEvent Queue
eksekusi, yang membuat penundaan dijamin.3.
Kali ini ajax callback.
4.
Node dapat dianggap sebagai raja pengkodean asinkron. Di sini fungsi yang ditandai terdaftar sebagai penangan panggilan balik yang akan dieksekusi setelah membaca file yang ditentukan.
5.
Janji yang jelas (sesuatu akan dilakukan di masa depan) tidak sinkron. lihat Apa perbedaan antara Ditangguhkan, Berjanji dan Masa Depan dalam JavaScript?
https://www.quora.com/Whats-the-difference-between-a-promise-and-a-callback-in-Javascript
sumber