→ Untuk penjelasan yang lebih umum tentang perilaku async dengan contoh berbeda, silakan lihat Mengapa variabel saya tidak berubah setelah saya memodifikasinya di dalam suatu fungsi? - Referensi kode tidak sinkron
→ Jika Anda sudah memahami masalahnya, lewati ke solusi yang mungkin di bawah ini.
Masalah
The A di Ajax singkatan asynchronous . Itu berarti mengirim permintaan (atau lebih tepatnya menerima respons) dikeluarkan dari alur eksekusi normal. Dalam contoh Anda, $.ajax
kembali segera dan pernyataan berikutnya return result;
,, dijalankan sebelum fungsi yang Anda lewati sebagaisuccess
panggilan balik bahkan dipanggil.
Berikut ini analogi yang diharapkan membuat perbedaan antara aliran sinkron dan asinkron lebih jelas:
Sinkronis
Bayangkan Anda menelepon seorang teman dan memintanya mencari sesuatu untuk Anda. Meskipun mungkin perlu waktu, Anda menunggu di telepon dan menatap ke angkasa, sampai teman Anda memberi Anda jawaban yang Anda butuhkan.
Hal yang sama terjadi ketika Anda membuat panggilan fungsi yang berisi kode "normal":
function findItem() {
var item;
while(item_not_found) {
// search
}
return item;
}
var item = findItem();
// Do something with item
doSomethingElse();
Meskipun findItem
mungkin membutuhkan waktu lama untuk dieksekusi, kode apa pun yang muncul var item = findItem();
harus menunggu sampai fungsi mengembalikan hasilnya.
Tidak sinkron
Anda menelepon teman Anda lagi untuk alasan yang sama. Tetapi kali ini Anda memberi tahu dia bahwa Anda sedang terburu-buru dan dia harus menelepon Anda kembali di ponsel Anda. Anda menutup telepon, meninggalkan rumah dan melakukan apa pun yang Anda rencanakan. Setelah teman Anda menelepon Anda kembali, Anda berurusan dengan informasi yang dia berikan kepada Anda.
Itulah tepatnya yang terjadi ketika Anda melakukan permintaan Ajax.
findItem(function(item) {
// Do something with item
});
doSomethingElse();
Alih-alih menunggu respons, eksekusi berlanjut segera dan pernyataan setelah panggilan Ajax dieksekusi. Untuk mendapatkan respons pada akhirnya, Anda menyediakan fungsi untuk dipanggil setelah respons diterima, panggilan balik (perhatikan sesuatu? Panggilan balik ?). Pernyataan apa pun yang datang setelah panggilan itu dieksekusi sebelum panggilan balik dipanggil.
Solusi)
Merangkul sifat asinkron dari JavaScript!Sementara operasi asinkron tertentu menyediakan mitra sinkron (demikian juga "Ajax"), umumnya tidak disarankan untuk menggunakannya, terutama dalam konteks browser.
Mengapa ini buruk menurut Anda?
JavaScript berjalan di utas UI peramban dan proses yang berjalan lama akan mengunci UI, membuatnya tidak responsif. Selain itu, ada batas atas pada waktu eksekusi untuk JavaScript dan browser akan menanyakan kepada pengguna apakah akan melanjutkan eksekusi atau tidak.
Semua ini benar-benar pengalaman pengguna yang buruk. Pengguna tidak akan dapat mengetahui apakah semuanya berfungsi dengan baik atau tidak. Selanjutnya, efeknya akan lebih buruk bagi pengguna dengan koneksi yang lambat.
Berikut ini kita akan melihat tiga solusi berbeda yang semuanya membangun di atas satu sama lain:
- Janji dengan
async/await
(ES2017 +, tersedia di browser lama jika Anda menggunakan transpiler atau regenerator)
- Callback (populer di simpul)
- Janji dengan
then()
(ES2015 +, tersedia di browser lama jika Anda menggunakan salah satu dari banyak perpustakaan janji)
Ketiganya tersedia di browser saat ini, dan node 7+.
ES2017 +: Janji dengan async/await
Versi ECMAScript yang dirilis pada 2017 memperkenalkan dukungan level sintaksis untuk fungsi-fungsi asinkron. Dengan bantuan async
dan await
, Anda dapat menulis asinkron dalam "gaya sinkron". Kode masih asinkron, tetapi lebih mudah dibaca / dimengerti.
async/await
dibangun di atas janji: suatu async
fungsi selalu mengembalikan janji. await
"membuka" janji dan entah menghasilkan nilai yang dijanjikan dengan janji itu atau menimbulkan kesalahan jika janji itu ditolak.
Penting: Anda hanya dapat menggunakan await
di dalam suatu async
fungsi. Saat ini, tingkat atas await
belum didukung, jadi Anda mungkin harus membuat IIFE async ( Ekspresi Fungsi yang Segera Diminta ) untuk memulaiasync
konteks.
Anda dapat membaca lebih lanjut tentang async
dan await
di MDN.
Berikut adalah contoh yang dibangun di atas penundaan di atas:
// Using 'superagent' which will return a promise.
var superagent = require('superagent')
// This is isn't declared as `async` because it already returns a promise
function delay() {
// `delay` returns a promise
return new Promise(function(resolve, reject) {
// Only `delay` is able to resolve or reject the promise
setTimeout(function() {
resolve(42); // After 3 seconds, resolve the promise with value 42
}, 3000);
});
}
async function getAllBooks() {
try {
// GET a list of book IDs of the current user
var bookIDs = await superagent.get('/user/books');
// wait for 3 seconds (just for the sake of this example)
await delay();
// GET information about each book
return await superagent.get('/books/ids='+JSON.stringify(bookIDs));
} catch(error) {
// If any of the awaited promises was rejected, this catch block
// would catch the rejection reason
return null;
}
}
// Start an IIFE to use `await` at the top level
(async function(){
let books = await getAllBooks();
console.log(books);
})();
Saat peramban dan simpul versi mendukung async/await
. Anda juga dapat mendukung lingkungan yang lebih lama dengan mengubah kode Anda ke ES5 dengan bantuan regenerator (atau alat yang menggunakan regenerator, seperti Babel ).
Biarkan fungsi menerima panggilan balik
Panggilan balik hanyalah sebuah fungsi yang diteruskan ke fungsi lain. Fungsi lain itu dapat memanggil fungsi yang dilewati setiap kali siap. Dalam konteks proses asinkron, callback akan dipanggil setiap kali proses asinkron dilakukan. Biasanya, hasilnya diteruskan ke panggilan balik.
Dalam contoh pertanyaan, Anda dapat foo
menerima panggilan balik dan menggunakannya sebagai success
panggilan balik. Jadi ini
var result = foo();
// Code that depends on 'result'
menjadi
foo(function(result) {
// Code that depends on 'result'
});
Di sini kita mendefinisikan fungsi "inline" tetapi Anda dapat melewati semua referensi fungsi:
function myCallback(result) {
// Code that depends on 'result'
}
foo(myCallback);
foo
sendiri didefinisikan sebagai berikut:
function foo(callback) {
$.ajax({
// ...
success: callback
});
}
callback
akan merujuk ke fungsi yang kita lewati foo
ketika kita menyebutnya dan kita hanya meneruskannya ke success
. Yaitu setelah permintaan Ajax berhasil, $.ajax
akan memanggil callback
dan meneruskan tanggapan ke panggilan balik (yang dapat dirujuk denganresult
, karena ini adalah bagaimana kami mendefinisikan callback).
Anda juga dapat memproses respons sebelum meneruskannya ke callback:
function foo(callback) {
$.ajax({
// ...
success: function(response) {
// For example, filter the response
callback(filtered_response);
}
});
}
Lebih mudah menulis kode menggunakan panggilan balik daripada yang terlihat. Lagipula, JavaScript di browser sangat didorong oleh peristiwa (peristiwa DOM). Menerima respons Ajax tidak lain adalah suatu peristiwa.
Kesulitan dapat muncul ketika Anda harus bekerja dengan kode pihak ketiga, tetapi sebagian besar masalah dapat diselesaikan dengan hanya memikirkan alur aplikasi.
ES2015 +: Janji dengan itu ()
The Promise API adalah fitur baru dari ECMAScript 6 (ES2015), tetapi memiliki baik dukungan browser sudah. Ada juga banyak perpustakaan yang mengimplementasikan standar API Janji dan menyediakan metode tambahan untuk memudahkan penggunaan dan komposisi fungsi asinkron (mis. Bluebird ).
Janji adalah wadah untuk nilai masa depan . Ketika janji menerima nilai ( diselesaikan ) atau dibatalkan ( ditolak ), ia memberi tahu semua "pendengar" yang ingin mengakses nilai ini.
Keuntungan daripada panggilan balik biasa adalah mereka memungkinkan Anda untuk memisahkan kode Anda dan mereka lebih mudah untuk menulis.
Berikut adalah contoh sederhana menggunakan janji:
function delay() {
// `delay` returns a promise
return new Promise(function(resolve, reject) {
// Only `delay` is able to resolve or reject the promise
setTimeout(function() {
resolve(42); // After 3 seconds, resolve the promise with value 42
}, 3000);
});
}
delay()
.then(function(v) { // `delay` returns a promise
console.log(v); // Log the value once it is resolved
})
.catch(function(v) {
// Or do something else if it is rejected
// (it would not happen in this example, since `reject` is not called).
});
Diterapkan ke panggilan Ajax kami, kami bisa menggunakan janji seperti ini:
function ajax(url) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(this.responseText);
};
xhr.onerror = reject;
xhr.open('GET', url);
xhr.send();
});
}
ajax("/echo/json")
.then(function(result) {
// Code depending on result
})
.catch(function() {
// An error occurred
});
Menjelaskan semua keuntungan yang dijanjikan menawarkan di luar cakupan jawaban ini, tetapi jika Anda menulis kode baru, Anda harus mempertimbangkannya dengan serius. Mereka memberikan abstraksi dan pemisahan kode Anda.
Informasi lebih lanjut tentang janji: HTML5 rocks - JavaScript Promises
Catatan: objek yang ditangguhkan jQuery
Objek yang ditangguhkan adalah implementasi kustom janji-janji jQuery (sebelum API Janji distandarisasi). Mereka berperilaku hampir seperti janji tetapi mengekspos API yang sedikit berbeda.
Setiap metode Ajax dari jQuery sudah mengembalikan "objek yang ditangguhkan" (sebenarnya janji objek yang ditangguhkan) yang bisa Anda kembalikan dari fungsi Anda:
function ajax() {
return $.ajax(...);
}
ajax().done(function(result) {
// Code depending on result
}).fail(function() {
// An error occurred
});
Catatan: Janji Gotcha
Perlu diingat bahwa janji dan objek yang ditangguhkan hanyalah wadah untuk nilai masa depan, mereka bukan nilai itu sendiri. Misalnya, anggap Anda memiliki yang berikut:
function checkPassword() {
return $.ajax({
url: '/password',
data: {
username: $('#username').val(),
password: $('#password').val()
},
type: 'POST',
dataType: 'json'
});
}
if (checkPassword()) {
// Tell the user they're logged in
}
Kode ini salah mengerti masalah asinkron di atas. Secara khusus, $.ajax()
tidak membekukan kode saat memeriksa halaman '/ kata sandi' di server Anda - ia mengirim permintaan ke server dan saat menunggu, ia segera mengembalikan objek jQuery Ajax Deferred, bukan respons dari server. Itu berarti if
pernyataan akan selalu mendapatkan objek Ditangguhkan ini, perlakukan sebagai true
, dan lanjutkan seolah-olah pengguna masuk. Tidak bagus.
Namun perbaikannya mudah:
checkPassword()
.done(function(r) {
if (r) {
// Tell the user they're logged in
} else {
// Tell the user their password was bad
}
})
.fail(function(x) {
// Tell the user something bad happened
});
Tidak disarankan: Panggilan "Ajax" yang disinkronkan
Seperti yang saya sebutkan, beberapa operasi asinkron (!) Memiliki mitra sinkron. Saya tidak menganjurkan penggunaannya, tetapi demi kelengkapan, berikut adalah cara Anda melakukan panggilan sinkron:
Tanpa jQuery
Jika Anda langsung menggunakan XMLHTTPRequest
objek, berikan false
argumen ketiga .open
.
jQuery
Jika Anda menggunakan jQuery , Anda dapat mengatur async
opsi ke false
. Perhatikan bahwa opsi ini sudah tidak digunakan lagi sejak jQuery 1.8. Anda kemudian dapat masih menggunakan success
panggilan balik atau mengakses responseText
properti objek jqXHR :
function foo() {
var jqXHR = $.ajax({
//...
async: false
});
return jqXHR.responseText;
}
Jika Anda menggunakan metode jQuery Ajax lainnya, seperti $.get
, $.getJSON
, dll, Anda harus mengubahnya ke $.ajax
(karena Anda hanya bisa memberikan parameter konfigurasi untuk $.ajax
).
Kepala! Tidak mungkin untuk membuat permintaan JSONP sinkron . JSONP pada dasarnya selalu asinkron (satu alasan lagi untuk tidak mempertimbangkan opsi ini).
If you use any other jQuery AJAX method, such as $.get, $.getJSON, etc., you have them to $.ajax.
(Ya, saya menyadari nick saya agak ironis dalam kasus ini)foo
dipanggil dan fungsi dilewatkan ke sana (foo(function(result) {....});
)?result
digunakan di dalam fungsi ini dan merupakan respons dari permintaan Ajax. Untuk merujuk ke fungsi ini, parameter pertama dari foo dipanggilcallback
dan ditetapkansuccess
sebagai ganti fungsi anonim. Jadi,$.ajax
akan meneleponcallback
saat permintaan itu berhasil. Saya mencoba menjelaskannya sedikit lagi.$.getJSON
jika Anda ingin agar permintaan Ajax sinkron. Namun, Anda seharusnya tidak ingin acara disinkronkan, sehingga tidak berlaku. Anda harus menggunakan panggilan balik atau janji untuk menangani respons, seperti yang dijelaskan sebelumnya dalam jawaban.Jika Anda tidak menggunakan jQuery dalam kode Anda, jawaban ini untuk Anda
Kode Anda harus berupa sesuatu seperti ini:
Felix Kling melakukan pekerjaan dengan baik menulis jawaban untuk orang yang menggunakan jQuery untuk AJAX, saya telah memutuskan untuk memberikan alternatif bagi orang yang tidak.
( Catatan, bagi mereka yang menggunakan
fetch
API baru , Sudut atau janji saya telah menambahkan jawaban lain di bawah )Apa yang Anda hadapi
Ini adalah ringkasan singkat "Penjelasan masalah" dari jawaban yang lain, jika Anda tidak yakin setelah membaca ini, baca itu.
The A di AJAX singkatan asynchronous . Itu berarti mengirim permintaan (atau lebih tepatnya menerima respons) dikeluarkan dari alur eksekusi normal. Dalam contoh Anda,
.send
kembali segera dan pernyataan berikutnyareturn result;
,, dijalankan sebelum fungsi yang Anda berikan sebagaisuccess
panggilan balik bahkan dipanggil.Ini berarti ketika Anda kembali, pendengar yang Anda tetapkan belum menjalankan, yang berarti nilai yang Anda kembalikan belum ditentukan.
Berikut ini analogi sederhana
(Biola)
Nilai yang
a
dikembalikan adalahundefined
karenaa=5
bagian belum dieksekusi. AJAX bertindak seperti ini, Anda mengembalikan nilainya sebelum server mendapat kesempatan untuk memberi tahu browser Anda apa nilainya.Salah satu solusi yang mungkin untuk masalah ini adalah dengan mengkode ulang secara aktif , memberi tahu program Anda apa yang harus dilakukan ketika perhitungan selesai.
Ini disebut CPS . Pada dasarnya, kami melewati
getFive
tindakan untuk dilakukan ketika selesai, kami memberi tahu kode kami bagaimana bereaksi ketika suatu acara selesai (seperti panggilan AJAX kami, atau dalam hal ini timeout).Penggunaan akan:
Yang seharusnya mengingatkan "5" ke layar. (Biola) .
Solusi yang memungkinkan
Pada dasarnya ada dua cara bagaimana mengatasi ini:
1. Sinkron AJAX - Jangan lakukan itu !!
Adapun AJAX sinkron, jangan lakukan itu! Jawaban Felix menimbulkan beberapa argumen kuat tentang mengapa itu ide yang buruk. Singkatnya, ini akan membekukan browser pengguna sampai server mengembalikan respons dan menciptakan pengalaman pengguna yang sangat buruk. Berikut ini ringkasan singkat yang diambil dari MDN tentang alasan:
Jika Anda harus melakukannya, Anda dapat mengibarkan bendera: Ini caranya:
2. Merestrukturisasi kode
Biarkan fungsi Anda menerima panggilan balik. Dalam contoh kode
foo
dapat dibuat untuk menerima panggilan balik. Kami akan memberi tahu kode kami bagaimana bereaksi ketikafoo
selesai.Begitu:
Menjadi:
Di sini kami melewati fungsi anonim, tetapi kami dapat dengan mudah meneruskan referensi ke fungsi yang sudah ada, membuatnya terlihat seperti:
Untuk detail lebih lanjut tentang bagaimana desain callback semacam ini dilakukan, periksa jawaban Felix.
Sekarang, mari kita mendefinisikan foo sendiri untuk bertindak sesuai
(biola)
Kami sekarang telah membuat fungsi foo kami menerima tindakan untuk dijalankan ketika AJAX selesai dengan sukses, kami dapat memperluas ini lebih jauh dengan memeriksa apakah status respons tidak 200 dan bertindak sesuai (buat penangan yang gagal dan semacamnya). Secara efektif menyelesaikan masalah kami.
Jika Anda masih kesulitan memahami ini, baca panduan memulai AJAX di MDN.
sumber
onload
handler, yang hanya menyala saatreadyState
itu4
. Tentu saja, itu tidak didukung di IE8. (iirc, mungkin perlu konfirmasi.)XMLHttpRequest 2 (pertama-tama baca jawaban dari Benjamin Gruenbaum & Felix Kling )
Jika Anda tidak menggunakan jQuery dan menginginkan XMLHttpRequest 2 pendek yang bagus yang berfungsi di browser modern dan juga di browser seluler, saya sarankan untuk menggunakannya dengan cara ini:
Seperti yang Anda lihat:
Ada dua cara untuk mendapatkan respons panggilan Ajax ini (tiga menggunakan nama var XMLHttpRequest):
Yang paling sederhana:
Atau jika karena alasan tertentu Anda
bind()
dipanggil ke kelas:Contoh:
Atau (yang di atas adalah fungsi anonim yang lebih baik selalu menjadi masalah):
Tidak ada yang lebih mudah.
Sekarang beberapa orang mungkin akan mengatakan bahwa lebih baik menggunakan onreadystatechange atau bahkan nama variabel XMLHttpRequest. Itu salah.
Lihat fitur lanjutan XMLHttpRequest
Ini mendukung semua * browser modern. Dan saya dapat mengonfirmasi karena saya menggunakan pendekatan ini karena XMLHttpRequest 2 ada. Saya tidak pernah mengalami masalah pada semua browser yang saya gunakan.
onreadystatechange hanya berguna jika Anda ingin mendapatkan header pada status 2.
Menggunakan
XMLHttpRequest
nama variabel adalah kesalahan besar lainnya karena Anda perlu menjalankan panggilan balik di dalam penutup onload / oreadystatechange jika Anda kehilangan itu.Sekarang jika Anda menginginkan sesuatu yang lebih kompleks menggunakan pos dan FormData Anda dapat dengan mudah memperluas fungsi ini:
Sekali lagi ... ini adalah fungsi yang sangat singkat, tetapi dapat & dikirim.
Contoh penggunaan:
Atau berikan elemen bentuk penuh (
document.getElementsByTagName('form')[0]
):Atau atur beberapa nilai khusus:
Seperti yang Anda lihat, saya tidak menerapkan sinkronisasi ... itu hal yang buruk.
Setelah mengatakan itu ... mengapa tidak melakukannya dengan cara yang mudah?
Seperti yang disebutkan dalam komentar, penggunaan kesalahan && sinkron benar-benar mematahkan inti jawabannya. Yang merupakan cara singkat yang baik untuk menggunakan Ajax dengan cara yang benar?
Penangan kesalahan
Dalam skrip di atas, Anda memiliki penangan kesalahan yang didefinisikan secara statis sehingga tidak mengganggu fungsi. Penangan kesalahan dapat digunakan untuk fungsi lain juga.
Tetapi untuk benar-benar keluar dari kesalahan, satu - satunya cara adalah dengan menulis URL yang salah dalam hal ini setiap browser melakukan kesalahan.
Penangan kesalahan mungkin berguna jika Anda mengatur header kustom, mengatur responseType ke buffer array gumpalan atau apa pun ...
Bahkan jika Anda melewati 'POSTAPAPAP' sebagai metode itu tidak akan menimbulkan kesalahan.
Bahkan jika Anda meneruskan 'fdggdgilfdghfldj' sebagai formdata, itu tidak akan menimbulkan kesalahan.
Dalam kasus pertama kesalahan ada di dalam di
displayAjax()
bawahthis.statusText
sebagaiMethod not Allowed
.Dalam kasus kedua, itu hanya berfungsi. Anda harus memeriksa di sisi server jika Anda mengirimkan data posting yang benar.
cross-domain tidak diizinkan melempar kesalahan secara otomatis.
Dalam respons kesalahan, tidak ada kode kesalahan.
Hanya ada
this.type
yang diatur ke kesalahan.Mengapa menambahkan penangan kesalahan jika Anda benar-benar tidak memiliki kendali atas kesalahan? Sebagian besar kesalahan dikembalikan di dalamnya dalam fungsi callback
displayAjax()
.Jadi: Tidak perlu untuk pemeriksaan kesalahan jika Anda dapat menyalin dan menempel URL dengan benar. ;)
PS: Sebagai tes pertama saya menulis x ('x', displayAjax) ..., dan benar-benar mendapat tanggapan ... ??? Jadi saya memeriksa folder tempat HTML itu berada, dan ada file bernama 'x.xml'. Jadi, bahkan jika Anda lupa ekstensi file Anda XMLHttpRequest 2 AKAN MENCARI ITU . SAYA LOL
Baca file secara sinkron
Jangan lakukan itu.
Jika Anda ingin memblokir browser untuk sementara memuat
.txt
file besar yang bagus, sinkron.Sekarang kamu bisa melakukannya
Tidak ada cara lain untuk melakukan ini dengan cara yang tidak sinkron. (Ya, dengan loop setTimeout ... tapi serius?)
Poin lainnya adalah ... jika Anda bekerja dengan API atau hanya file daftar Anda sendiri atau apa pun yang Anda selalu gunakan fungsi berbeda untuk setiap permintaan ...
Hanya jika Anda memiliki halaman di mana Anda memuat selalu XML / JSON yang sama atau apa pun yang Anda butuhkan hanya satu fungsi. Dalam hal ini, modifikasi sedikit fungsi Ajax dan ganti b dengan fungsi khusus Anda.
Fungsi-fungsi di atas adalah untuk penggunaan dasar.
Jika Anda ingin MEMPERPANJANG fungsi ...
Ya kamu bisa.
Saya menggunakan banyak API dan salah satu fungsi pertama yang saya integrasikan ke setiap halaman HTML adalah fungsi Ajax pertama dalam jawaban ini, dengan hanya DAPATKAN ...
Tetapi Anda dapat melakukan banyak hal dengan XMLHttpRequest 2:
Saya membuat download manager (menggunakan rentang di kedua sisi dengan resume, filereader, filesystem), berbagai konverter resizers gambar menggunakan kanvas, mengisi database web SQL dengan gambar base64 dan banyak lagi ... Tetapi dalam kasus ini Anda harus membuat fungsi hanya untuk itu tujuan ... terkadang Anda membutuhkan gumpalan, buffer array, Anda dapat mengatur tajuk, mengganti mimetype dan ada banyak lagi ...
Tapi pertanyaannya di sini adalah bagaimana mengembalikan respons Ajax ... (Saya menambahkan cara mudah.)
sumber
x
,ajax
atauxhr
mungkin lebih baik :)). Saya tidak melihat bagaimana alamat mengembalikan respons dari panggilan AJAX. (seseorang masih bisa melakukannyavar res = x("url")
dan tidak mengerti mengapa itu tidak berhasil;)) Di samping catatan - itu akan keren jika Anda kembalic
dari metode sehingga pengguna dapat mengaitkanerror
dll.2.ajax is meant to be async.. so NO var res=x('url')..
Itulah inti dari pertanyaan dan jawaban ini :)Jika Anda menggunakan janji, jawaban ini untuk Anda.
Ini berarti AngularJS, jQuery (dengan ditangguhkan), pengganti XHR asli (fetch), EmberJS, save BackboneJS atau pustaka node apa pun yang mengembalikan janji.
Kode Anda harus berupa sesuatu seperti ini:
Felix Kling melakukan pekerjaan dengan baik menulis jawaban untuk orang yang menggunakan jQuery dengan panggilan balik untuk AJAX. Saya punya jawaban untuk XHR asli. Jawaban ini untuk penggunaan umum janji baik di frontend atau backend.
Masalah intinya
Model konkurensi JavaScript di browser dan di server dengan NodeJS / io.js tidak sinkron dan reaktif .
Setiap kali Anda memanggil metode yang mengembalikan janji,
then
penangan selalu dieksekusi secara tidak sinkron - yaitu, setelah kode di bawahnya yang tidak ada dalam.then
penangan.Ini berarti ketika Anda kembali
data
padathen
handler Anda menetapkan tidak mengeksekusi belum. Ini pada gilirannya berarti bahwa nilai yang Anda kembalikan belum disetel ke nilai waktu yang tepat.Berikut ini analogi sederhana untuk masalah ini:
Nilai
data
iniundefined
karenadata = 5
bagian belum dieksekusi. Ini kemungkinan akan dieksekusi dalam sedetik tetapi pada saat itu tidak relevan dengan nilai yang dikembalikan.Karena operasi belum terjadi (AJAX, panggilan server, IO, timer) Anda mengembalikan nilai sebelum permintaan mendapat kesempatan untuk memberi tahu kode Anda apa nilainya.
Salah satu solusi yang mungkin untuk masalah ini adalah dengan mengkode ulang secara aktif , memberi tahu program Anda apa yang harus dilakukan ketika perhitungan selesai. Janji aktif memungkinkan ini dengan menjadi temporal (sensitif waktu) di alam.
Rekap cepat janji
Janji adalah nilai dari waktu ke waktu . Janji memiliki status, mereka mulai sebagai tertunda tanpa nilai dan dapat puas dengan:
Sebuah janji hanya dapat mengubah status satu kali setelah itu akan selalu tetap pada kondisi yang sama selamanya. Anda dapat melampirkan
then
penangan ke janji untuk mengekstraksi nilainya dan menangani kesalahan.then
penangan memungkinkan chaining panggilan. Janji dibuat dengan menggunakan API yang mengembalikannya . Misalnya, pengganti AJAX yang lebih modernfetch
atau$.get
janji pengembalian jQuery .Ketika kita memanggil
.then
janji dan mengembalikan sesuatu darinya - kita mendapat janji untuk nilai yang diproses . Jika kita mengembalikan janji lain kita akan mendapatkan hal-hal luar biasa, tapi mari kita pegang kuda kita.Dengan janji
Mari kita lihat bagaimana kita bisa menyelesaikan masalah di atas dengan janji-janji. Pertama, mari kita tunjukkan pemahaman kita tentang negara janji dari atas dengan menggunakan konstruktor Janji untuk membuat fungsi penundaan:
Sekarang, setelah kami mengonversi setTimeout untuk menggunakan janji, kita dapat menggunakannya
then
untuk membuatnya berhitung:Pada dasarnya, bukannya kembali nilai yang tidak bisa kita lakukan karena model concurrency - kita mengembalikan pembungkus untuk nilai yang kita dapat membukanya dengan
then
. Ini seperti sebuah kotak yang bisa kamu bukathen
.Menerapkan ini
Ini sama dengan panggilan API asli Anda, Anda dapat:
Jadi ini berfungsi juga. Kami telah mempelajari bahwa kami tidak dapat mengembalikan nilai dari panggilan yang sudah tidak sinkron tetapi kami dapat menggunakan janji dan membuat rantai untuk melakukan pemrosesan. Kami sekarang tahu cara mengembalikan respons dari panggilan asinkron.
ES2015 (ES6)
ES6 memperkenalkan generator yang merupakan fungsi yang dapat kembali di tengah dan kemudian melanjutkan titik mereka berada. Ini biasanya berguna untuk urutan, misalnya:
Adalah fungsi yang mengembalikan iterator di atas urutan
1,2,3,3,3,3,....
yang dapat diulang. Meskipun ini menarik sendiri dan membuka ruang untuk banyak kemungkinan, ada satu kasus menarik.Jika urutan yang kami hasilkan adalah urutan tindakan dan bukan angka - kami dapat menjeda fungsi setiap kali tindakan dihasilkan dan menunggu sebelum kami melanjutkan fungsi. Jadi, alih-alih urutan angka, kita membutuhkan urutan nilai masa depan - yaitu: janji.
Trik ini agak rumit tetapi sangat kuat memungkinkan kita menulis kode asinkron secara sinkron. Ada beberapa "pelari" yang melakukan ini untuk Anda, menulis satu adalah beberapa baris kode tetapi di luar cakupan jawaban ini. Saya akan menggunakan Bluebird di
Promise.coroutine
sini, tetapi ada pembungkus lain sepertico
atauQ.async
.Metode ini mengembalikan janji itu sendiri, yang dapat kita konsumsi dari coroutine lain. Sebagai contoh:
ES2016 (ES7)
Dalam ES7, ini lebih lanjut distandarisasi, ada beberapa proposal saat ini tetapi dalam semua itu Anda bisa
await
menjanjikan. Ini hanya "gula" (sintaks yang lebih bagus) untuk proposal ES6 di atas dengan menambahkanasync
danawait
kata kunci. Membuat contoh di atas:Itu masih mengembalikan janji sama saja :)
sumber
return await data.json();
?)Anda salah menggunakan Ajax. Idenya bukan untuk mengembalikan apa pun, tetapi menyerahkan data ke sesuatu yang disebut fungsi panggil balik, yang menangani data.
Itu adalah:
Mengembalikan apa pun dalam handler kirim tidak akan melakukan apa pun. Sebagai gantinya Anda harus menyerahkan data, atau melakukan apa yang Anda inginkan langsung di dalam fungsi keberhasilan.
sumber
success: handleData
dan itu akan berhasil.success
metode, itu lingkup sekitarnya$.ajax
.Solusi paling sederhana adalah membuat fungsi JavaScript dan memanggilnya untuk
success
panggilan balik Ajax .sumber
Saya akan menjawab dengan komik yang terlihat mengerikan dan digambar tangan. Gambar kedua adalah alasan mengapa
result
adaundefined
dalam contoh kode Anda.sumber
Angular1
Bagi orang yang menggunakan AngularJS , dapat menangani situasi ini menggunakan
Promises
.Di sini dikatakan,
Anda juga dapat menemukan penjelasan yang bagus di sini .
Contoh ditemukan dalam dokumen yang disebutkan di bawah ini.
Angular2 dan Selanjutnya
Masuk
Angular2
dengan melihat contoh berikut, tetapi disarankan untuk digunakanObservables
bersamaAngular2
.}
Anda dapat mengkonsumsinya dengan cara ini,
Lihat posting asli di sini. Tapi Typescript tidak mendukung Janji es6 asli , jika Anda ingin menggunakannya, Anda mungkin perlu plugin untuk itu.
Selain itu di sini adalah spesifikasi janji yang ditentukan di sini.
sumber
Sebagian besar jawaban di sini memberikan saran yang berguna untuk ketika Anda memiliki operasi async tunggal, tetapi kadang-kadang, ini muncul ketika Anda perlu melakukan operasi asinkron untuk setiap entri dalam array atau struktur daftar-suka lainnya. Godaan adalah melakukan ini:
Contoh:
Tampilkan cuplikan kode
Alasan yang tidak berfungsi adalah bahwa callback dari
doSomethingAsync
belum berjalan pada saat Anda mencoba menggunakan hasilnya.Jadi, jika Anda memiliki array (atau daftar sejenis) dan ingin melakukan operasi asink untuk setiap entri, Anda memiliki dua opsi: Lakukan operasi secara paralel (tumpang tindih), atau secara seri (satu demi satu secara berurutan).
Paralel
Anda dapat memulai semuanya dan melacak berapa banyak panggilan balik yang Anda harapkan, dan kemudian menggunakan hasilnya ketika Anda mendapatkan banyak panggilan balik itu:
Contoh:
Tampilkan cuplikan kode
(Kita bisa menghapus
expecting
dan hanya menggunakanresults.length === theArray.length
, tapi itu membuat kita terbuka untuk kemungkinan yangtheArray
diubah saat panggilan luar biasa ...)Perhatikan bagaimana kami menggunakan
index
dariforEach
untuk menyimpan hasilnyaresults
posisi yang sama dengan entri yang terkait, bahkan jika hasilnya tiba di luar urutan (karena panggilan async tidak harus lengkap dalam urutan di mana mereka mulai).Tetapi bagaimana jika Anda perlu mengembalikan hasil dari suatu fungsi? Seperti yang ditunjukkan oleh jawaban lain, Anda tidak bisa; Anda harus memiliki fungsi Anda menerima dan memanggil panggilan balik (atau mengembalikan Janji ). Ini versi panggilan balik:
Contoh:
Tampilkan cuplikan kode
Atau ini versi mengembalikan
Promise
gantinya:Tentu saja, jika
doSomethingAsync
melewati kami kesalahan, kami akan gunakanreject
untuk menolak janji ketika kami mendapat kesalahan.)Contoh:
Tampilkan cuplikan kode
(Atau secara bergantian, Anda bisa membuat pembungkus untuk
doSomethingAsync
itu mengembalikan janji, dan kemudian melakukan yang di bawah ...)Jika
doSomethingAsync
memberi Anda Janji , Anda dapat menggunakanPromise.all
:Jika Anda tahu itu
doSomethingAsync
akan mengabaikan argumen kedua dan ketiga, Anda bisa langsung menyampaikannyamap
(map
panggilan balik dengan tiga argumen, tetapi kebanyakan orang hanya menggunakan sebagian besar pertama kali):Contoh:
Tampilkan cuplikan kode
Perhatikan bahwa
Promise.all
selesaikan janjinya dengan serangkaian hasil dari semua janji yang Anda berikan saat semuanya dipenuhi, atau tolak janjinya ketika yang pertama dari janji yang Anda berikan itu tolak.Seri
Misalkan Anda tidak ingin operasi dilakukan secara paralel? Jika Anda ingin menjalankannya satu per satu, Anda harus menunggu setiap operasi selesai sebelum Anda memulai yang berikutnya. Berikut adalah contoh fungsi yang melakukan itu dan memanggil panggilan balik dengan hasilnya:
(Karena kami melakukan pekerjaan secara seri, kami hanya dapat menggunakan
results.push(result)
karena kami tahu kami tidak akan mendapatkan hasil yang salah. Di atas kita bisa menggunakanresults[index] = result;
, tetapi dalam beberapa contoh berikut kita tidak memiliki indeks menggunakan.)Contoh:
Tampilkan cuplikan kode
(Atau, sekali lagi, buat pembungkus untuk
doSomethingAsync
itu memberi Anda janji dan lakukan yang di bawah ini ...)Jika
doSomethingAsync
memberi Anda Janji, jika Anda dapat menggunakan sintaks ES2017 + (mungkin dengan transpiler seperti Babel ), Anda dapat menggunakanasync
fungsi denganfor-of
danawait
:Contoh:
Tampilkan cuplikan kode
Jika Anda tidak dapat menggunakan sintaks ES2017 + (belum), Anda dapat menggunakan variasi pada "Janji pengurangan" (ini lebih kompleks daripada pengurangan Janji yang biasa karena kami tidak meneruskan hasil dari satu ke yang berikutnya, tetapi sebagai gantinya mengumpulkan hasil mereka dalam array):
Contoh:
Tampilkan cuplikan kode
... yang kurang rumit dengan fungsi panah ES2015 + :
Contoh:
Tampilkan cuplikan kode
sumber
if (--expecting === 0)
bagian dari kode itu bekerja? Versi callback dari solusi Anda berfungsi baik untuk saya, saya hanya tidak mengerti bagaimana, dengan pernyataan itu, Anda memeriksa jumlah respons yang diselesaikan. Menghargai itu hanya karena kurangnya pengetahuan di pihak saya. Apakah ada cara alternatif agar cek dapat ditulis?expecting
dimulai dengan nilaiarray.length
, yaitu berapa banyak permintaan yang akan kami buat. Kami tahu bahwa panggilan balik itu tidak akan dipanggil sampai semua permintaan itu dimulai. Dalam callback,if (--expecting === 0)
lakukan ini: 1. Penguranganexpecting
(kami telah menerima respons, jadi kami mengharapkan satu tanggapan lebih sedikit) dan jika nilai setelah penurunan adalah 0 (kami tidak mengharapkan tanggapan lagi), kami selesai!results
tidak ada). :-) Memperbaikinya.Lihat contoh ini:
Seperti yang Anda lihat
getJoke
adalah mengembalikan janji yang telah diselesaikan (itu diselesaikan saat kembalires.data.value
). Jadi Anda menunggu sampai permintaan $ http.get selesai dan kemudian console.log (res.joke) dieksekusi (sebagai aliran asinkron normal).Ini adalah plnkr:
http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/
Cara ES6 (async - tunggu)
sumber
Ini adalah salah satu tempat di mana dua cara pengikatan data atau konsep penyimpanan yang digunakan dalam banyak kerangka kerja JavaScript baru akan bekerja sangat baik untuk Anda ...
Jadi jika Anda menggunakan Angular, React atau kerangka kerja lain yang melakukan dua cara pengikatan data atau konsep menyimpan masalah ini hanya diperbaiki untuk Anda, jadi dengan kata lain, hasilnya adalah
undefined
pada tahap pertama, jadi Anda sudah mendapatkannyaresult = undefined
sebelum Anda menerima data, maka segera setelah Anda mendapatkan hasilnya, itu akan diperbarui dan ditugaskan ke nilai baru yang respons panggilan Ajax Anda ...Tetapi bagaimana Anda bisa melakukannya dalam javascript atau jQuery murni misalnya ketika Anda bertanya dalam pertanyaan ini?
Anda dapat menggunakan panggilan balik , janji dan baru-baru ini diamati untuk menanganinya untuk Anda, misalnya dalam janji-janji kami memiliki beberapa fungsi seperti
success()
atauthen()
yang akan dieksekusi ketika data Anda siap untuk Anda, sama dengan fungsi panggil balik atau berlangganan pada diamati .Misalnya dalam kasus Anda yang Anda gunakan jQuery , Anda dapat melakukan sesuatu seperti ini:
Untuk informasi lebih lanjut, pelajari tentang janji dan yang dapat diamati yang merupakan cara baru untuk melakukan hal ini.
sumber
$.ajax({url: "api/data", success: fooDone.bind(this)});
Ini adalah masalah yang sangat umum yang kita hadapi saat berjuang dengan 'misteri' JavaScript. Biarkan saya mencoba mengungkap misteri ini hari ini.
Mari kita mulai dengan fungsi JavaScript sederhana:
Itu adalah panggilan fungsi sinkron sederhana (di mana setiap baris kode 'selesai dengan tugasnya' sebelum yang berikutnya secara berurutan), dan hasilnya sama seperti yang diharapkan.
Sekarang mari kita tambahkan sedikit twist, dengan memperkenalkan sedikit delay pada fungsi kita, sehingga semua baris kode tidak 'selesai' secara berurutan. Dengan demikian, ini akan meniru perilaku asinkron fungsi:
Jadi begitulah, penundaan itu baru saja merusak fungsionalitas yang kami harapkan! Tetapi apa yang sebenarnya terjadi? Sebenarnya cukup logis jika Anda melihat kode. fungsi
foo()
, setelah eksekusi, tidak mengembalikan apa-apa (dengan demikian nilai yang dikembalikanundefined
), tetapi ia memulai timer, yang mengeksekusi fungsi setelah 1s untuk mengembalikan 'wohoo'. Tetapi seperti yang Anda lihat, nilai yang ditugaskan ke bar adalah barang yang langsung dikembalikan dari foo (), yang artinya tidak adilundefined
.Jadi, bagaimana kita mengatasi masalah ini?
Mari kita tanyakan fungsi kita untuk PROMISE . Janji benar-benar tentang apa artinya: itu berarti bahwa fungsi menjamin Anda untuk memberikan dengan output apa pun di masa depan. jadi mari kita lihat dalam aksi untuk masalah kecil kita di atas:
Dengan demikian, ringkasannya adalah - untuk menangani fungsi asinkron seperti panggilan berbasis ajax dll., Anda dapat menggunakan janji dengan
resolve
nilai (yang ingin Anda kembalikan). Jadi, singkatnya Anda menyelesaikan nilai alih-alih mengembalikan , dalam fungsi asinkron.UPDATE (Janji dengan async / menunggu)
Selain menggunakan
then/catch
untuk bekerja dengan janji-janji, ada satu pendekatan lagi. Idenya adalah untuk mengenali fungsi asinkron dan kemudian menunggu janji untuk diselesaikan, sebelum pindah ke baris kode berikutnya. Itu masih sajapromises
bawah kap, tetapi dengan pendekatan sintaksis yang berbeda. Agar lebih jelas, Anda dapat menemukan perbandingan di bawah ini:lalu / tangkap versi:
versi async / menunggu:
sumber
Pendekatan lain untuk mengembalikan nilai dari fungsi asinkron, adalah untuk meneruskan objek yang akan menyimpan hasil dari fungsi asinkron.
Berikut adalah contoh yang sama:
Saya menggunakan
result
objek untuk menyimpan nilai selama operasi asinkron. Ini memungkinkan hasilnya tersedia bahkan setelah pekerjaan asinkron.Saya sering menggunakan pendekatan ini. Saya akan tertarik untuk mengetahui seberapa baik pendekatan ini bekerja di mana kabel hasil kembali melalui modul yang terlibat.
sumber
result
. Ini berfungsi karena Anda membaca variabel setelah fungsi async selesai.Sementara janji dan panggilan balik berfungsi dengan baik dalam banyak situasi, itu adalah rasa sakit di belakang untuk mengekspresikan sesuatu seperti:
Anda akhirnya akan melalui
async1
; periksa apakahname
tidak terdefinisi atau tidak dan panggil panggilan balik yang sesuai.Meskipun tidak apa - apa dalam contoh-contoh kecil, itu akan mengganggu ketika Anda memiliki banyak kasus serupa dan penanganan kesalahan yang terlibat.
Fibers
membantu dalam memecahkan masalah.Anda dapat checkout proyek di sini .
sumber
async-await
jika Anda menggunakan beberapa versi terbaru dari node. Jika seseorang terjebak dengan versi yang lebih lama, mereka dapat menggunakan metode ini.Contoh berikut yang saya tulis menunjukkan caranya
Contoh kerja ini mandiri. Ini akan menentukan objek permintaan sederhana yang menggunakan
XMLHttpRequest
objek jendela untuk melakukan panggilan. Ini akan menentukan fungsi sederhana untuk menunggu banyak janji untuk diselesaikan.Konteks. Contohnya adalah menanyakan titik akhir Spotify Web API untuk mencari
playlist
objek untuk serangkaian string kueri yang diberikan:Untuk setiap item, Janji baru akan mem-blok -
ExecutionBlock
, parsing hasilnya, jadwalkan satu set janji baru berdasarkan pada array hasil, yaitu daftaruser
objek Spotify dan jalankan panggilan HTTP baru dalamExecutionProfileBlock
asinkron.Anda kemudian dapat melihat struktur Janji bersarang, yang memungkinkan Anda menelurkan beberapa panggilan HTTP bersarang tidak bersinkronisasi, dan bergabung dengan hasil dari setiap subset panggilan
Promise.all
.CATATAN
search
API Spotify Terbaru akan membutuhkan token akses untuk ditentukan dalam header permintaan:Jadi, Anda menjalankan contoh berikut ini Anda harus memasukkan token akses Anda di header permintaan:
Saya telah banyak membahas solusi ini di sini .
sumber
Jawaban singkatnya adalah, Anda harus menerapkan panggilan balik seperti ini:
sumber
2017 jawaban: sekarang Anda dapat melakukan apa yang Anda inginkan di setiap browser dan node saat ini
Ini cukup sederhana:
Ini versi kode Anda yang berfungsi:
await didukung di semua browser dan node 8 saat ini
sumber
await/async
Browser dapat dibagi menjadi tiga bagian:
1) Kejadian
2) API Web
3) Antrian Acara
Event Loop berjalan selamanya yaitu jenis infinite loop. Event Queue adalah tempat semua fungsi Anda didorong pada beberapa acara (contoh: klik) ini adalah satu per satu dilakukan antrian dan dimasukkan ke dalam loop acara yang menjalankan fungsi ini dan menyiapkannya sendiri untuk yang berikutnya setelah yang pertama dieksekusi. Ini berarti Eksekusi satu fungsi tidak dimulai sampai fungsi sebelum dalam antrian dieksekusi dalam loop peristiwa.
Sekarang mari kita berpikir kita mendorong dua fungsi dalam antrian satu untuk mendapatkan data dari server dan yang lain memanfaatkan data tersebut. Kami mendorong fungsi serverRequest () dalam antrian terlebih dahulu kemudian menggunakan fungsiiseiseData (). Fungsi serverRequest berjalan dalam event loop dan membuat panggilan ke server karena kita tidak pernah tahu berapa banyak waktu yang diperlukan untuk mendapatkan data dari server sehingga proses ini diharapkan memakan waktu dan jadi kita sibuk acara loop kita sehingga menggantung halaman kita, di situlah Web API berperan mengambil fungsi ini dari loop acara dan berurusan dengan server membuat loop acara bebas sehingga kita dapat menjalankan fungsi berikutnya dari antrian. Fungsi berikutnya dalam antrian adalah utiliseData () yang berjalan dalam loop tetapi karena tidak ada data yang tersedia ia pergi pemborosan dan eksekusi fungsi selanjutnya berlanjut sampai akhir antrian. (Ini disebut panggilan Async yaitu kita dapat melakukan sesuatu yang lain sampai kita mendapatkan data)
Misalkan saja fungsi serverRequest () kami memiliki pernyataan pengembalian dalam kode, ketika kami mendapatkan kembali data dari server Web API akan mendorongnya dalam antrian di akhir antrian. Karena terdesak di ujung antrian, kami tidak dapat memanfaatkan datanya karena tidak ada fungsi yang tersisa di antrian kami untuk memanfaatkan data ini. Dengan demikian, tidak mungkin mengembalikan sesuatu dari Panggilan Async.
Jadi Solusi untuk ini adalah panggilan balik atau janji .
Gambar dari salah satu jawaban di sini, Menjelaskan dengan benar penggunaan panggilan balik ... Kami memberikan fungsi kami (fungsi menggunakan data yang dikembalikan dari server) ke fungsi memanggil server.
Dalam Kode saya disebut sebagai
Callback Javscript.info
sumber
Anda dapat menggunakan perpustakaan khusus ini (ditulis menggunakan Janji) untuk membuat panggilan jarak jauh.
Contoh penggunaan sederhana:
sumber
Solusi lain adalah dengan mengeksekusi kode melalui nsynjs eksekusi berurutan .
Jika fungsi yang mendasarinya dijanjikan
nsynjs akan mengevaluasi semua janji secara berurutan, dan memasukkan hasil janji ke dalam
data
properti:Jika fungsi yang mendasarinya tidak dijanjikan
Langkah 1. Bungkus fungsi dengan callback ke nsynjs-aware wrapper (jika memiliki versi yang dijanjikan, Anda dapat melewati langkah ini):
Langkah 2. Masukkan logika sinkron ke dalam fungsi:
Langkah 3. Jalankan fungsi secara sinkron melalui nsynjs:
Nsynjs akan mengevaluasi semua operator dan ekspresi langkah demi langkah, menghentikan sementara eksekusi jika hasil dari beberapa fungsi yang lambat tidak siap.
Contoh lebih lanjut di sini: https://github.com/amaksr/nsynjs/tree/master/examples
sumber
ECMAScript 6 memiliki 'generator' yang memungkinkan Anda untuk dengan mudah memprogram dalam gaya asinkron.
Untuk menjalankan kode di atas Anda melakukan ini:
Jika Anda perlu menargetkan browser yang tidak mendukung ES6 Anda dapat menjalankan kode melalui Babel atau closure-compiler untuk menghasilkan ECMAScript 5.
Callback
...args
dibungkus dalam array dan dihancurkan ketika Anda membacanya sehingga polanya dapat mengatasi panggilan balik yang memiliki banyak argumen. Misalnya dengan simpul fs :sumber
Berikut adalah beberapa pendekatan untuk bekerja dengan permintaan asinkron:
Contoh: jQuery implementasi yang ditangguhkan untuk bekerja dengan beberapa permintaan
sumber
Kita menemukan diri kita di alam semesta yang tampak maju sepanjang dimensi yang kita sebut "waktu". Kami tidak benar-benar mengerti apa waktu itu, tetapi kami telah mengembangkan abstraksi dan kosa kata yang membuat kami beralasan dan membicarakannya: "masa lalu", "sekarang", "masa depan", "sebelum", "setelah".
Sistem komputer yang kami bangun - semakin banyak - memiliki waktu sebagai dimensi penting. Hal-hal tertentu diatur untuk terjadi di masa depan. Maka hal-hal lain perlu terjadi setelah hal-hal pertama itu akhirnya terjadi. Ini adalah gagasan dasar yang disebut "asinkronisitas". Dalam dunia kita yang semakin berjejaring, kasus asynchronicity yang paling umum adalah menunggu sistem jarak jauh untuk menanggapi beberapa permintaan.
Pertimbangkan sebuah contoh. Anda memanggil pengantar susu dan memesan susu. Ketika datang, Anda ingin memasukkannya ke dalam kopi Anda. Anda tidak dapat memasukkan susu ke dalam kopi Anda sekarang, karena belum ada di sini. Anda harus menunggu sampai datang sebelum memasukkannya ke dalam kopi Anda. Dengan kata lain, berikut ini tidak akan berfungsi:
Karena JS tidak memiliki cara untuk mengetahui bahwa perlu menunggu untuk
order_milk
sampai akhir sebelum dijalankanput_in_coffee
. Dengan kata lain, itu tidak tahu bahwa ituorder_milk
tidak sinkron - adalah sesuatu yang tidak akan menghasilkan susu sampai beberapa waktu ke depan. JS, dan bahasa deklaratif lainnya menjalankan satu pernyataan demi satu tanpa menunggu.Pendekatan JS klasik untuk masalah ini, mengambil keuntungan dari fakta bahwa JS mendukung fungsi sebagai objek kelas satu yang dapat diteruskan, adalah meneruskan fungsi sebagai parameter ke permintaan asinkron, yang kemudian akan dipanggil ketika telah selesai tugasnya di masa depan. Itu adalah pendekatan "panggilan balik". Ini terlihat seperti ini:
order_milk
dimulai, memesan susu, kemudian, kapan dan hanya ketika itu tiba, itu memanggilput_in_coffee
.Masalah dengan pendekatan callback ini adalah bahwa ia mencemari semantik normal dari suatu fungsi yang melaporkan hasilnya
return
; sebagai gantinya, fungsi tidak boleh melaporkan hasilnya dengan memanggil panggilan balik yang diberikan sebagai parameter. Juga, pendekatan ini dapat dengan cepat menjadi sulit ketika berhadapan dengan urutan kejadian yang lebih panjang. Sebagai contoh, katakanlah saya ingin menunggu susu dimasukkan ke dalam kopi, dan baru kemudian melakukan langkah ketiga, yaitu minum kopi. Saya akhirnya perlu menulis sesuatu seperti ini:di mana saya melewati
put_in_coffee
kedua susu untuk dimasukkan ke dalamnya, dan juga tindakan (drink_coffee
) untuk mengeksekusi setelah susu dimasukkan. Kode tersebut menjadi sulit untuk ditulis, dibaca, dan didebug.Dalam hal ini, kita dapat menulis ulang kode dalam pertanyaan sebagai:
Masukkan janji
Ini adalah motivasi untuk gagasan "janji", yang merupakan jenis nilai tertentu yang mewakili semacam hasil di masa depan atau tidak sinkron . Itu bisa mewakili sesuatu yang sudah terjadi, atau yang akan terjadi di masa depan, atau mungkin tidak pernah terjadi sama sekali. Janji-janji memiliki satu metode, yang dinamai
then
, tempat Anda meloloskan suatu tindakan untuk dieksekusi ketika hasil yang dijanjikan janji telah terwujud.Dalam hal susu dan kopi kami, kami merancang
order_milk
untuk mengembalikan janji atas kedatangan susu, kemudian menetapkanput_in_coffee
sebagaithen
tindakan, sebagai berikut:Satu keuntungan dari ini adalah kita dapat merangkai ini bersama-sama untuk menciptakan urutan kejadian di masa depan ("rantai"):
Mari kita terapkan janji untuk masalah khusus Anda. Kami akan membungkus logika permintaan kami di dalam suatu fungsi, yang mengembalikan janji:
Sebenarnya, semua yang kami lakukan ditambahkan
return
ke panggilan$.ajax
. Ini berfungsi karena jQuery$.ajax
sudah mengembalikan semacam janji. (Dalam praktiknya, tanpa masuk ke perincian, kami lebih suka untuk membungkus panggilan ini sehingga untuk mengembalikan janji nyata, atau menggunakan beberapa alternatif untuk$.ajax
melakukannya.) Sekarang, jika kita ingin memuat file dan menunggu sampai selesai dan lalu lakukan sesuatu, kita bisa langsung mengatakannyacontohnya,
Saat menggunakan janji, kami akhirnya memberikan banyak fungsi
then
, jadi sering kali membantu menggunakan fungsi panah gaya ES6 yang lebih ringkas:Kata
async
kunciTetapi masih ada sesuatu yang agak tidak memuaskan karena harus menulis kode satu arah jika sinkron dan cara yang sangat berbeda jika tidak sinkron. Untuk sinkron, kami menulis
tetapi jika
a
tidak sinkron, dengan janji kita harus menulisDi atas, kami berkata, "JS tidak memiliki cara untuk mengetahui bahwa ia perlu menunggu panggilan pertama selesai sebelum mengeksekusi yang kedua". Bukankah lebih baik jika ada adalah beberapa cara untuk memberitahu JS itu? Ternyata ada -
await
kata kunci, yang digunakan di dalam jenis fungsi khusus yang disebut fungsi "async". Fitur ini adalah bagian dari versi ES yang akan datang, tetapi sudah tersedia dalam transpiler seperti Babel yang diberikan preset yang tepat. Ini memungkinkan kita untuk sekadar menulisDalam kasus Anda, Anda dapat menulis sesuatu seperti
sumber
Jawaban singkat :
foo()
Metode Anda segera kembali, sementara$ajax()
panggilan dijalankan secara serempak setelah fungsi kembali . Masalahnya kemudian bagaimana atau di mana menyimpan hasil yang diambil oleh panggilan async setelah itu kembali.Beberapa solusi telah diberikan di utas ini. Mungkin cara termudah adalah dengan mengirimkan objek ke
foo()
metode, dan menyimpan hasilnya di anggota objek itu setelah panggilan async selesai.Perhatikan bahwa panggilan ke
foo()
masih tidak menghasilkan apa-apa yang berguna. Namun, hasil dari panggilan async sekarang akan disimpan diresult.response
.sumber
Gunakan
callback()
fungsi di dalamfoo()
kesuksesan. Coba dengan cara ini. Sederhana dan mudah dimengerti.sumber
Pertanyaannya adalah:
yang BISA diartikan sebagai:
Solusinya adalah menghindari callback, dan menggunakan kombinasi dari Janji dan async / menunggu .
Saya ingin memberi contoh untuk permintaan Ajax.
(Meskipun dapat ditulis dalam Javascript, saya lebih suka menulisnya dengan Python, dan mengompilasinya ke Javascript menggunakan Transcrypt . Akan cukup jelas.)
Pertama mari kita aktifkan penggunaan JQuery, agar
$
tersedia sebagaiS
:Tetapkan fungsi yang mengembalikan Janji , dalam hal ini panggilan Ajax:
Gunakan kode asinkron seolah-olah sinkron :
sumber
Menggunakan Janji
Jawaban paling sempurna untuk pertanyaan ini adalah menggunakan
Promise
.Pemakaian
Tapi tunggu...!
Ada masalah dengan menggunakan janji!
Mengapa kita harus menggunakan Janji kebiasaan kita sendiri?
Saya menggunakan solusi ini untuk sementara waktu sampai saya menemukan ada kesalahan di browser lama:
Jadi saya memutuskan untuk mengimplementasikan kelas Promise saya sendiri untuk ES3 di bawah kompiler js jika tidak ditentukan. Cukup tambahkan kode ini sebelum kode utama Anda dan kemudian gunakan Promise dengan aman!
sumber
Tentu saja ada banyak pendekatan seperti permintaan sinkron, janji, tetapi dari pengalaman saya, saya pikir Anda harus menggunakan pendekatan callback. Itu wajar untuk perilaku Javascript asinkron. Jadi, cuplikan kode Anda dapat ditulis ulang sedikit berbeda:
sumber
Daripada melempar kode pada Anda, ada 2 konsep yang merupakan kunci untuk memahami bagaimana JS menangani panggilan balik dan asinkronisitas. (Apakah itu sepatah kata pun?)
Model Lingkaran Acara dan Konkurensi
Ada tiga hal yang perlu Anda waspadai; Antrian; loop acara dan tumpukan
Secara luas, istilah sederhana, loop acara seperti manajer proyek, itu terus-menerus mendengarkan fungsi yang ingin dijalankan dan berkomunikasi antara antrian dan tumpukan.
Setelah menerima pesan untuk menjalankan sesuatu, ia menambahkannya ke antrian. Antrian adalah daftar hal-hal yang menunggu untuk dieksekusi (seperti permintaan AJAX Anda). bayangkan seperti ini:
Ketika salah satu dari pesan ini akan mengeksekusinya muncul pesan dari antrian dan membuat stack, stack adalah segalanya yang perlu dieksekusi oleh JS untuk melakukan instruksi dalam pesan. Jadi, dalam contoh kita, disuruh menelepon
foobarFunc
Jadi apa pun yang perlu dieksekusi foobarFunc (dalam kasus kami
anotherFunction
) akan didorong ke stack. dieksekusi, dan kemudian dilupakan - loop acara kemudian akan pindah ke hal berikutnya dalam antrian (atau mendengarkan pesan)Kuncinya di sini adalah urutan eksekusi. Itu adalah
KAPAN sesuatu akan berjalan
Saat Anda melakukan panggilan menggunakan AJAX ke pihak eksternal atau menjalankan kode asinkron apa pun (misalnya, setTimeout), Javascript bergantung pada respons sebelum dapat melanjutkan.
Pertanyaan besarnya adalah kapan ia akan mendapat respons? Jawabannya adalah kita tidak tahu - jadi loop acara sedang menunggu pesan untuk mengatakan "hey jalankan saya". Jika JS hanya menunggu pesan itu secara sinkron, aplikasi Anda akan membeku dan akan menyedot. Jadi JS melakukan eksekusi item berikutnya dalam antrian sambil menunggu pesan untuk ditambahkan kembali ke antrian.
Itu sebabnya dengan fungsionalitas asinkron kami menggunakan hal-hal yang disebut panggilan balik . Agak seperti janji secara harfiah. Seperti dalam Saya berjanji untuk mengembalikan sesuatu di beberapa titik jQuery menggunakan panggilan balik khusus yang disebut
deffered.done
deffered.fail
dandeffered.always
(antara lain). Anda dapat melihat semuanya di siniJadi yang perlu Anda lakukan adalah melewatkan fungsi yang dijanjikan untuk dieksekusi di beberapa titik dengan data yang diteruskan ke sana.
Karena panggilan balik tidak dieksekusi segera tetapi di lain waktu penting untuk meneruskan referensi ke fungsi yang tidak dieksekusi. begitu
sehingga sebagian besar waktu (tetapi tidak selalu) Anda akan melewati
foo
tidakfoo()
Semoga itu masuk akal. Ketika Anda menemukan hal-hal seperti ini yang membingungkan - saya sangat merekomendasikan membaca dokumentasi sepenuhnya untuk setidaknya mendapatkan pemahaman tentang itu. Ini akan membuat Anda menjadi pengembang yang jauh lebih baik.
sumber
Menggunakan ES2017 Anda harus memiliki ini sebagai deklarasi fungsi
Dan menjalankannya seperti ini.
Atau sintaksis Janji
sumber