Bagaimana cara mengembalikan respons dari panggilan asinkron?

Saya memiliki fungsi foo yang membuat permintaan Ajax. Bagaimana cara mengembalikan jawaban dari foo ?

Saya mencoba mengembalikan nilai dari callback success , dan juga menetapkan respons ke variabel lokal di dalam fungsi dan mengembalikannya, tetapi tidak satu pun dari metode ini mengembalikan respons.

 function foo() { var result; $.ajax({ url: '...', success: function(response) { result = response; // return response; // <- I tried that one as well } }); return result; } var result = foo(); // It always ends up being `undefined`. 
4687
08 янв. Felix Kling mengatur 08 Jan 2013-01-08 20:06 '13 pada 20:06 2013-01-08 20:06
@ 39 jawaban
  • 1
  • 2

→ Untuk penjelasan yang lebih umum tentang perilaku asinkron dalam berbagai contoh, lihat. Mengapa variabel saya tidak berubah setelah saya mengubahnya di dalam suatu fungsi? - tautan asinkron ke kode

→ Jika Anda sudah memahami masalahnya, buka kemungkinan solusi di bawah ini.

Masalah ini

A dalam Ajax berarti asinkron . Ini berarti bahwa mengirim permintaan (atau, lebih tepatnya, menerima tanggapan) dihapus dari aliran eksekusi normal. Dalam contoh Anda, $.ajax segera kembali, dan hasil pernyataan berikutnya return result; , dijalankan sebelum fungsi yang Anda lewati sebagai panggilan balik success bahkan dipanggil.

Berikut ini analogi yang, semoga, menjelaskan perbedaan antara aliran sinkron dan asinkron:

sinkron

Bayangkan Anda menelepon seorang teman dan memintanya untuk menemukan sesuatu untuk Anda. Meskipun mungkin perlu waktu, Anda menunggu di telepon dan melihat ke ruang 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(); 

Sekalipun mungkin perlu waktu lama untuk mengeksekusi findItem , kode apa pun yang mengikuti var item = findItem(); harus menunggu hingga fungsi mengembalikan hasil.

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 semua yang Anda rencanakan. Segera setelah teman Anda menelepon Anda kembali, Anda akan berurusan dengan informasi yang dia berikan kepada Anda.

Inilah yang terjadi ketika Anda membuat permintaan Ajax.

 findItem(function(item) { // Do something with item }); doSomethingElse(); 

Alih-alih menunggu jawaban, eksekusi dilanjutkan segera dan pernyataan dieksekusi setelah panggilan Ajax. Untuk akhirnya menerima respons, Anda menyediakan fungsi yang akan dipanggil setelah menerima respons, panggilan balik (catat sesuatu, "panggilan balik"). Pernyataan apa pun setelah panggilan ini dieksekusi sebelum panggilan panggilan balik.


Solusi

Terapkan sifat asinkron javascript! Meskipun beberapa operasi asinkron menyediakan mitra sinkron (seperti "Ajax"), mereka biasanya tidak disarankan untuk digunakan, terutama dalam konteks browser.

Mengapa ini buruk, Anda bertanya?

JavaScript berjalan di utas UI peramban, dan proses apa pun yang panjang memblokir antarmuka pengguna, yang membuatnya tidak responsif. Selain itu, ada batas runtime atas 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 mengatakan apakah semuanya berfungsi dengan baik atau tidak. Selain itu, efeknya akan lebih buruk bagi pengguna dengan koneksi yang lambat.

Selanjutnya, kami melihat tiga solusi berbeda yang dibangun di atas satu sama lain:

  • Janji dengan async/await (ES2017 +, tersedia di browser lama jika Anda menggunakan transporter atau regenerator)
  • Panggilan balik (populer di situs)
  • Janji dengan then() (ES2015 +, tersedia di browser lama jika Anda menggunakan salah satu dari banyak perpustakaan janji)

Ketiganya tersedia di browser saat ini dan simpul 7+.


ES2017 +: janji dengan async/await menunggu

Versi ECMAScript yang dirilis pada 2017 memperkenalkan dukungan sintaks untuk fungsi asinkron. Dengan async dan await Anda dapat menulis secara tidak sinkron dalam "gaya sinkron". Kode masih asinkron, tetapi lebih mudah dibaca / dimengerti.

async/await didasarkan pada janji: fungsi async selalu mengembalikan janji. await "membuka" janji dan baik hasil dalam arti janji itu diselesaikan dengan atau memberikan kesalahan jika janji itu ditolak.

Penting: Anda hanya dapat menggunakan await di dalam fungsi async . Saat ini, pada level tertinggi, await belum didukung, jadi Anda mungkin harus membuat IIFE asinkron memulai konteks async .

Anda dapat membaca lebih lanjut tentang async dan await di MDN.

Berikut adalah contoh yang didasarkan pada keterlambatan 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); })(); 

Versi browser saat ini dan dukungan host async/await . Anda juga dapat mempertahankan lingkungan yang lebih lama dengan mengonversi kode Anda ke ES5 menggunakan 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 ini dapat memanggil fungsi yang dilewati setiap kali siap. Dalam konteks proses asinkron, panggilan balik akan dipanggil setiap kali proses asinkron dijalankan. Biasanya hasilnya dikirim ke callback.

Dalam contoh pertanyaan, Anda dapat memaksa foo untuk menerima panggilan balik dan menggunakannya sebagai panggilan balik yang success . 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 memberikan referensi apa pun ke fungsi:

 function myCallback(result) { // Code that depends on 'result' } foo(myCallback); 

foo sendiri didefinisikan sebagai berikut:

 function foo(callback) { $.ajax({ // ... success: callback }); } 

Panggilan callback akan merujuk ke fungsi yang kami berikan kepada kami ketika kami menyebutnya, dan kami hanya meneruskannya ke success . Yaitu segera setelah permintaan Ajax berhasil, $.ajax akan memanggil callback dan $.ajax respons ke panggilan balik (yang dapat direferensikan menggunakan result , karena ini adalah bagaimana kami mendefinisikan panggilan balik).

Anda juga dapat memproses respons sebelum meneruskannya ke callback:

 function foo(callback) { $.ajax({ // ... success: function(response) { // For example, filter the response callback(filtered_response); } }); } 

Menulis kode menggunakan panggilan balik lebih mudah daripada yang terlihat. Lagipula, JavaScript di browser sangat bergantung pada peristiwa (peristiwa DOM). Mendapatkan respons Ajax tidak lebih dari sebuah 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 ()

API Janji adalah fitur baru dari ECMAScript 6 (ES2015), tetapi sudah memiliki dukungan browser yang baik. Ada juga banyak perpustakaan yang mengimplementasikan Janji API standar dan menyediakan metode tambahan untuk menyederhanakan penggunaan dan komposisi fungsi asinkron (misalnya, burung bluebird ).

Janji adalah wadah untuk nilai masa depan. Ketika sebuah janji menerima nilai (diizinkan) atau ketika dibatalkan (ditolak), ia memberi tahu semua "pendengar" yang ingin mengakses nilai ini.

Keuntungan daripada panggilan balik sederhana adalah mereka memungkinkan Anda untuk memisahkan kode Anda, dan lebih mudah untuk menyusunnya.

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). }); 

Sehubungan dengan panggilan Ajax kami, kami dapat menggunakan janji-janji berikut:

 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 }); 

Deskripsi semua manfaat yang mereka janjikan akan tawarkan berada di luar cakupan jawaban ini, tetapi jika Anda menulis kode baru, Anda harus mempertimbangkannya dengan serius. Mereka memberikan abstraksi yang sangat baik dan pemisahan kode Anda.

Info lebih lanjut tentang janji: HTML5 - batu - janji JavaScript

Catatan: jQuery objek yang tertunda

Objek yang ditangguhkan adalah implementasi kustom dari janji di jQuery (sebelum standarisasi API Janji). Mereka hampir berperilaku seperti janji, tetapi memperlihatkan API yang sedikit berbeda.

Setiap metode jQuery Ajax sudah mengembalikan "objek yang tertunda" (sebenarnya janji dari objek yang tertunda), yang Anda dapat kembali dari fungsinya:

 function ajax() { return $.ajax(...); } ajax().done(function(result) { // Code depending on result }).fail(function() { // An error occurred }); 

Catatan: janji itu ternyata

Ingatlah bahwa janji dan objek yang ditangguhkan hanyalah wadah untuk nilai masa depan, 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, sambil menunggu, segera mengembalikan objek jQuery Ajax Deferred, bukan respons dari server. Ini berarti bahwa if akan selalu menerima objek yang ditangguhkan ini, memprosesnya sebagai true dan bertindak seolah-olah pengguna login. Tidak bagus

Tapi perbaiki dengan 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 direkomendasikan: panggilan sinkron "Ajax"

Seperti yang saya katakan, beberapa (!) Operasi asinkron memiliki mitra sinkron. Saya tidak menganjurkan penggunaannya, tetapi demi kelengkapan, inilah cara Anda melakukan panggilan sinkron:

Tanpa jQuery

Jika Anda secara >XMLHTTPRequest , berikan false sebagai argumen ketiga .open .

Jquery

Jika Anda menggunakan jQuery , Anda dapat mengatur parameter async ke false . Harap dicatat bahwa opsi ini sudah tidak digunakan lagi sejak jQuery 1.8. Kemudian Anda masih bisa menggunakan panggilan balik success atau akses ke properti responseText objek jqXHR :

 function foo() { var jqXHR = $.ajax({ //... async: false }); return jqXHR.responseText; } 

Jika Anda menggunakan metode jQuery Ajax lainnya, seperti $.getJSON , $.getJSON , dll., Anda harus mengubahnya ke $.ajax (karena Anda hanya dapat mengirimkan parameter konfigurasi ke $.ajax ).

Hati-hati Tidak dapat membuat permintaan JSONP sinkron. JSONP selalu asinkron (alasan lain untuk tidak mempertimbangkan opsi ini).

5011
08 янв. Jawabannya diberikan oleh Felix Kling 08 Jan 2013-01-08 20:06 '13 pada 20:06 2013-01-08 20:06

Jika Anda tidak menggunakan jQuery dalam kode Anda, jawaban ini untuk Anda.

Kode Anda harus seperti ini:

 function foo() { var httpRequest = new XMLHttpRequest(); httpRequest.open('GET', "/echo/json"); httpRequest.send(); return httpRequest.responseText; } var result = foo(); // always ends up being 'undefined' 

Felix Kling melakukan pekerjaan yang luar biasa menulis jawaban untuk orang yang menggunakan jQuery untuk AJAX, saya memutuskan untuk memberikan alternatif bagi orang yang tidak.

( Harap perhatikan bahwa bagi mereka yang menggunakan API baru, Angular, atau janji, saya menambahkan jawaban lain di bawah )


Apa yang Anda temui

Ini adalah ringkasan singkat dari "Penjelasan masalah" dari jawaban lain, jika Anda tidak yakin, setelah membaca ini, baca ini.

A dalam AJAX berarti asinkron . Ini berarti bahwa mengirim permintaan (atau, lebih tepatnya, menerima tanggapan) dihapus dari aliran eksekusi normal. Dalam contoh Anda, .send segera dikembalikan, dan pernyataan berikutnya return result; dieksekusi sebelum fungsi yang Anda lewati sebagai panggilan balik success bahkan dipanggil.

Ini berarti bahwa ketika Anda kembali, pendengar yang Anda tentukan belum terpenuhi, yang berarti bahwa nilai yang Anda kembalikan tidak ditentukan.

Analogi sederhana

 function getFive(){ var a; setTimeout(function(){ a=5; },10); return a; } 

(Feeddle)

Nilai pengembalian a tidak undefined , karena bagian a=5 belum dieksekusi. AJAX melakukan ini, Anda mengembalikan nilainya sebelum server dapat memberi tahu browser Anda apa nilainya.

Salah satu solusi yang mungkin untuk masalah ini adalah menggunakan kembali kode, memberi tahu program Anda tentang apa yang harus dilakukan ketika perhitungan selesai.

 function onComplete(a){ // When the code completes, do this alert(a); } function getFive(whenDone){ var a; setTimeout(function(){ a=5; whenDone(a); },10); } 

Ini disebut CPS . Pada dasarnya, kami getFive tindakan yang perlu dilakukan ketika selesai, kami memberi tahu kode kami bagaimana bereaksi ketika acara berakhir (misalnya, panggilan AJAX kami atau dalam hal ini time-out).

Gunakan:

 getFive(onComplete); 

Yang seharusnya mengingatkan "5" di layar. (Biola) .

Kemungkinan solusi

Pada dasarnya ada dua cara untuk mengatasi masalah ini:

  • Buat panggilan AJAX sinkron (panggil dia SJAX).
  • Merestrukturisasi kode Anda agar berfungsi dengan benar dengan callback.

1. Sinkron AJAX - jangan lakukan itu !!

Adapun AJAX sinkron, jangan lakukan itu! Tanggapan Felix memberikan beberapa argumen kuat mengapa ini adalah ide yang buruk. Untuk meringkas, itu akan membekukan browser pengguna sampai server mengembalikan respons dan menciptakan antarmuka pengguna yang sangat buruk. Berikut ini ringkasan MDN lainnya tentang alasan:

XMLHttpRequest mendukung komunikasi sinkron dan asinkron. Namun secara umum, permintaan asinkron lebih disukai daripada permintaan sinkron karena alasan kinerja.

Singkatnya, permintaan sinkronisasi eksekusi kode blok ...... ini dapat menyebabkan masalah serius ...

Jika Anda perlu melakukan ini, Anda dapat melewati benderanya: Begini caranya:

 var request = new XMLHttpRequest(); request.open('GET', 'yourURL', false); // `false` makes the request synchronous request.send(null); if (request.status === 200) {// That HTTP for 'ok' console.log(request.responseText); } 

2. Kode Restrukturisasi

Biarkan fungsi Anda menerima panggilan balik. Dalam contoh tersebut, kode foo dapat dibuat untuk menerima panggilan balik. Kami akan memberi tahu kode kami bagaimana bereaksi ketika foo selesai.

Jadi:

 var result = foo(); // code that depends on `result` goes here 

menjadi:

 foo(function(result) { // code that depends on `result` }); 

Di sini kami melewati fungsi anonim, tetapi kami hanya bisa meneruskan tautan ke fungsi yang ada, menjadikannya seperti ini:

 function myHandler(result) { // code that depends on `result` } foo(myHandler); 

Untuk informasi lebih lanjut tentang bagaimana jenis panggilan balik ini dijalankan, periksa respons Felix.

Sekarang mari kita mendefinisikan foo sendiri untuk bertindak sesuai

 function foo(callback) { var httpRequest = new XMLHttpRequest(); httpRequest.onload = function(){ // when the request is loaded callback(httpRequest.responseText);// we're calling our method }; httpRequest.open('GET', "/echo/json"); httpRequest.send(); } 

(biola)

Sekarang fungsi foo kami menerima tindakan yang dimulai ketika AJAX berhasil diselesaikan, kami dapat melanjutkan ini dengan memeriksa apakah status respons 200 merespons dan bertindak sesuai (buat penangan kesalahan, dll.). Solusi efektif untuk masalah kita.

Jika Anda masih kesulitan memahami hal ini, baca Panduan Memulai AJAX di MDN.

953
30 мая '13 в 2:30 2013-05-30 02:30 Balas diberikan oleh Benjamin Gruenbaum 30 Mei, '13 pukul 2:30 2013-05-30 02:30

XMLHttpRequest 2 (Pertama baca jawaban dari Benjamin Grünbaum dan Felix Kling )

Jika Anda tidak menggunakan jQuery dan ingin mendapatkan XMLHttpRequest 2 pendek yang bagus dan berfungsi di browser modern, serta di browser seluler, saya sarankan menggunakannya sebagai berikut:

 function ajax(a, b, c){ // URL, callback, just a placeholder c = new XMLHttpRequest; c.open('GET', a); c.onload = b; c.send() } 

Seperti yang Anda lihat:

  1. Ini lebih pendek dari semua fungsi lain yang terdaftar.
  2. Callback dilakukan secara >
  3. Ada beberapa situasi lain yang saya tidak ingat yang membuat XMLHttpRequest 1 mengganggu.

Ada dua cara untuk mendapatkan jawaban untuk panggilan Ajax ini (tiga menggunakan nama variabel XMLHttpRequest):

Yang paling sederhana:

 this.response 

Atau, jika karena alasan tertentu Anda bind() panggilan balik dengan kelas:

 e.target.response 

Contoh:

 function callback(e){ console.log(this.response); } ajax('URL', callback); 

Atau (fungsi anonim yang lebih baik di atas selalu menjadi masalah):

 ajax('URL', function(e){console.log(this.response)}); 

Tidak ada yang lebih mudah.

Sekarang beberapa orang mungkin akan mengatakan bahwa lebih baik menggunakan onreadystatechange atau bahkan nama variabel XMLHttpRequest. Ini salah.

Jelajahi fitur-fitur canggih XMLHttpRequest

Semua browser modern didukung. Dan saya dapat mengonfirmasi bahwa saya menggunakan pendekatan ini karena ada XMLHttpRequest 2. Saya tidak pernah memiliki masalah di semua browser yang saya gunakan.

onreadystatechange hanya berguna jika Anda ingin mendapatkan header dalam status 2.

Menggunakan nama variabel XMLHttpRequest adalah kesalahan besar lainnya, karena Anda perlu menelepon kembali ke dalam onload / oreadystatechange penutupan, jika Anda kehi>


Sekarang, jika Anda menginginkan sesuatu yang lebih kompleks menggunakan pos dan FormData, Anda dapat dengan mudah memperluas fungsi ini:

 function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val},placeholder c = new XMLHttpRequest; c.open(e||'get', a); c.onload = b; c.send(d||null) } 

Sekali lagi ... ini adalah fungsi yang sangat singkat, tetapi menerima dan menerbitkan.

Contoh penggunaan:

 x(url, callback); // By default it get so no need to set x(url, callback, 'post', {'key': 'val'}); // No need to set post data 

Atau kirimkan elemen formulir lengkap ( document.getElementsByTagName('form')[0] ):

 var fd = new FormData(form); x(url, callback, 'post', fd); 

Atau atur beberapa nilai khusus:

 var fd = new FormData(); fd.append('key', 'val') x(url, callback, 'post', fd); 

Seperti yang Anda lihat, saya tidak menerapkan sinkronisasi ... ini buruk.

Karena itu ... mengapa tidak melakukannya dengan cara yang sederhana?


Seperti disebutkan dalam komentar, penggunaan kesalahan sinkron sepenuhnya me>

Penangan kesalahan

 function x(a, b, e, d, c){ // URL, callback, method, formdata or {key:val}, placeholder c = new XMLHttpRequest; c.open(e||'get', a); c.onload = b; c.onerror = error; c.send(d||null) } function error(e){ console.log('--Error--', this.type); console.log('this: ', this); console.log('Event: ', e) } function displayAjax(e){ console.log(e, this); } x('WRONGURL', displayAjax); 

Dalam skrip di atas, Anda memiliki penangan kesalahan yang didefinisikan secara statis, sehingga tidak mengganggu fungsi. Обработчик ошибок может использоваться и для других функций.

Но чтобы действительно вывести ошибку, единственный способ - написать неправильный URL, и в этом случае каждый браузер выдает ошибку.