Eşzamansız bir çağrının yanıtı nasıl döndürülür?

Bir Ajax isteği yapan bir foo fonksiyonum var. Cevabı foo nasıl geri alabilirim?

success geri çağrısından bir değer döndürmeye çalıştım ve aynı zamanda işlev içindeki yerel bir değişkene yanıt atamadım ve geri döndürdüm, ancak bu yöntemlerden hiçbiri yanıt vermedi.

 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 08 Ocak ayarla 2013-01-08 20:06 '13 20:06 2013-01-08 20:06
@ 39 cevaplar
  • 1
  • 2

→ Farklı örneklerde eşzamansız davranışın daha genel bir açıklaması için bakınız: Bir fonksiyonun içinde değiştirdikten sonra değişkenim neden değişmiyor? - koda eşzamansız bağlantı

→ Sorunu daha önce anladıysanız, aşağıdaki olası çözümlere gidin.

Bu sorun

Ajax'ta A , asenkron demektir. Bu, normal bir uygulama akışından istek göndermenin (veya daha doğrusu bir yanıt almanın) kaldırılması anlamına gelir. $.ajax , $.ajax derhal geri döner ve bir sonraki deyim return result; , success geri arama olarak geçirdiğiniz fonksiyonun çağrılmasından önce bile yürütülür.

İşte, senkronize ve senkronize olmayan bir akım arasındaki farkı açıklığa kavuşturan bir benzetme:

senkron

Bir arkadaşını aradığını ve senin için bir şey bulmasını istediğini hayal et. Biraz zaman alabilir, ancak arkadaşınız size ihtiyacınız olan cevabı verene kadar telefonda bekleyin ve uzaya bakın.

Aynı şey "normal" kod içeren bir işlev çağrısı yaparken de olur:

 function findItem() { var item; while(item_not_found) { // search } return item; } var item = findItem(); // Do something with item doSomethingElse(); 

findItem çalıştırmak uzun zaman alabilir olsa da, izleyen herhangi bir kod var item = findItem(); fonksiyon bir sonuç verene kadar beklemelisiniz.

eşzamanlı olmayan

Aynı sebepten dolayı arkadaşını tekrar ara. Ama bu sefer ona acelesi olduğunu söylüyorsun ve seni cep telefonundan arayacak. Telefonu kapatırsın, evi terk eder ve planladığın her şeyi yaparsın. Arkadaşınız sizi aradığında, size verdiği bilgilerle ilgileneceksiniz.

Ajax isteğinde bulunduğunuzda gerçekleşen tam olarak budur.

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

Bir cevap beklemek yerine, yürütme derhal devam eder ve ifade bir Ajax çağrısından sonra yürütülür. Sonuçta bir yanıt almak için, yanıtı aldıktan sonra çağrılacak bir işlev, bir geri arama (bir şeyi not edin, "geri arama") sağlayın. Bu çağrıyı izleyen tüm ifadeler geri arama çağrısından önce yürütülür.


Çözüm (ler)

JavaScript'in asenkron yapısını benimseyin! Bazı eşzamansız işlemler eşzamanlı meslektaşları ("Ajax" gibi) sağlasa da, genellikle tarayıcı bağlamında kullanılması önerilmez.

Neden bu kadar kötü, soruyorsun?

JavaScript tarayıcı UI iş parçacığında çalışır ve uzun süren işlemler kullanıcı arabirimini engeller, bu da yanıt vermemesini sağlar. Ayrıca, JavaScript için bir üst çalışma zamanı sınırı vardır ve tarayıcı kullanıcıdan yürütmeye devam edip etmeyeceğini soracaktır.

Bütün bunlar gerçekten kötü bir kullanıcı deneyimi. Kullanıcı her şeyin düzgün çalışıp çalışmadığını söyleyemez. Ayrıca, yavaş bir bağlantıya sahip kullanıcılar için bu etki daha da kötüleşecektir.

Sonra, birbiri üzerine inşa edilmiş üç farklı çözüme bakıyoruz:

  • async/await beklemede olan vaatler (ES2017 +, taşıyıcı veya rejeneratör kullanıyorsanız, eski tarayıcılarda kullanılabilir)
  • Geri aramalar (sitede popüler)
  • then() sözlerle then() (Pek çok söz veren kütüphaneden birini kullanıyorsanız ES2015 +, eski tarayıcılarda kullanılabilir)

Üçü de mevcut tarayıcılarda ve 7+ düğümünde kullanılabilir.


ES2017 +: zaman async/await beklemede olanlarla vaat ediyor

2017'de yayınlanan ECMAScript sürümü, zaman uyumsuz işlevler için sözdizimi desteği sağlamıştır. Eşzamansız ve await "eşzamanlı stilde" eşzamansız olarak yazabilirsiniz. Kod hala eşzamansızdır ancak okunması / anlaşılması kolaydır.

async/await vaatlere dayanır: async işlevi her zaman bir söz verir. "vaadi" çözmeyi "bekle ve vaadin anlamı ya da vaadi reddedilirse hatayla sonuçlandı.

Önemli: Yalnızca bir zaman async işlevi içinde await kullanabilirsiniz. Şu anda, en üst düzeyde, henüz desteklenmiyor, bu nedenle asenkron IIFE'nin asenkron bağlamı başlatmasını sağlamak zorunda kalabilirsiniz.

Zaman async hakkında daha fazla bilgi edinebilir ve await .

İşte yukarıdaki gecikmeye dayanan bir örnek:

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

Tarayıcı ve ana bilgisayarın geçerli sürümleri, async/await destekler. Kodunuzu, bir rejeneratör kullanarak (veya Babel gibi bir jeneratör kullanarak araçlar) ES5'e dönüştürerek daha eski ortamları da koruyabilirsiniz.


Fonksiyonların geri aramaları kabul etmesine izin verin.

Geri çağırma, basitçe başka bir işleve geçen bir işlevdir. Bu diğer işlev, ne zaman hazır olursa, geçen bir işlevi çağırabilir. Zaman uyumsuz bir işlem bağlamında, zaman uyumsuz bir işlem yürütüldüğünde bir geri çağırma çağrılır. Normalde sonuç geri aramaya gönderilir.

Soru örneğinde, foo bir geri aramayı kabul etmeye ve bir geri arama olarak kullanmaya zorlayabilirsiniz. Yani bu

 var result = foo(); // Code that depends on 'result' 

Bu hale geliyor

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

Burada "satır içi" işlevini tanımlıyoruz, ancak işleve herhangi bir başvuru iletebilirsiniz:

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

foo kendisi şöyle tanımlanır:

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

callback , onu çağırdığımızda foo geçirdiğimiz fonksiyona atıfta bulunacak ve basitçe success geçeceğiz. yani Ajax isteği başarılı olur olmaz $.ajax callback çağıracak ve callback $.ajax yanıt $.ajax (bu, geri aramayı bu şekilde tanımladığımızdan dolayı result kullanılarak referans alınabilir).

Yanıtı geri aramaya yönlendirmeden önce de işleyebilirsiniz:

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

Geri aramaları kullanarak kod yazmak göründüğünden daha kolaydır. Sonuçta, tarayıcıdaki JavaScript, etkinliklere büyük ölçüde bağımlıdır (DOM etkinlikleri). Bir Ajax yanıtı almak bir olaydan başka bir şey değildir.
Üçüncü taraf kodlarla çalışmak zorunda olduğunuzda zorluklar ortaya çıkabilir, ancak çoğu sorun uygulamanın akışını düşünerek çözülebilir.


ES2015 +: sonra () ile vaat ediyor

Promise API , ECMAScript 6'nın (ES2015) yeni bir özelliğidir, ancak zaten iyi bir tarayıcı desteğine sahiptir . Ayrıca, standart API Promises'i uygulayan ve asenkron fonksiyonların (örneğin, bluebird ) kullanımını ve kompozisyonunu basitleştirmek için ek yöntemler sağlayan birçok kitaplık vardır.

Sözler gelecekteki değerler için kapsayıcıdır. Bir söz bir değer aldığında (izin verilir) veya iptal edildiğinde (reddedildiğinde), bu değere erişmek isteyen tüm “dinleyicilerini” bildirir.

Basit geri aramalara göre avantajı, kodunuzu ayırmanıza izin vermesi ve bunları oluşturması daha kolaydır.

İşte sözleri kullanmanın basit bir örneği:

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

Ajax çağrımızla ilgili olarak, aşağıdaki sözleri kullanabiliriz:

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

Sundukları tüm faydaların açıklaması, bu cevabın kapsamı dışındadır, ancak yeni bir kod yazıyorsanız, bunları ciddi şekilde düşünmelisiniz. Mükemmel bir soyutlama ve kodunuzun ayrılmasını sağlarlar.

Sözler hakkında daha fazla bilgi: HTML5 - rocks - JavaScript sözleri

Not: jQuery bekleyen nesneler

Ertelenen nesneler , jQuery'deki (Promise API'sinin standartlaştırılmasından önce) verilen taahhütlerin özel bir uygulamasıdır. Neredeyse vaat ettikleri gibi davranıyorlar, ancak biraz farklı bir API ortaya koyuyorlar.

Her jQuery Ajax yöntemi, zaten işlevinden basitçe döndürebileceğiniz bir "bekleyen nesne" (aslında bekleyen bir nesnenin vaadi) döndürür:

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

Not: söz ortaya çıktı

Vaat ve ertelenen nesnelerin, değerin kendisinin değil sadece gelecekteki değer için kaplar olduğunu unutmayın. Örneğin, aşağıdakilere sahip olduğunuzu varsayalım:

 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 } 

Bu kod yukarıdaki asenkron problemleri yanlış anlıyor. Özellikle, $.ajax() sunucunuzdaki '/ password' sayfasını kontrol ederken kodu dondurmaz; sunucuya bir istek gönderir ve beklerken sunucudan gelen yanıtı değil, hemen jQuery Ajax Deferred nesnesini döndürür. Bu, if bu ertelenmiş nesneyi her zaman alacağı, true olduğu gibi işlem yaptığı ve kullanıcı oturum açmış gibi davrandığı anlamına gelir. İyi değil

Ancak kolayca düzeltin:

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

Tavsiye edilmiyor: senkron aramalar "Ajax"

Dediğim gibi, bazı (!) Eşzamansız işlemlerin eşzamanlı benzerleri vardır. Kullanımlarını savunmuyorum, ancak bütünlük uğruna, senkronize bir arama yapmanız gerekenler:

JQuery yok

Doğrudan XMLHTTPRequest nesnesini kullanıyorsanız, üçüncü argüman olarak .open false .open .

JQuery

Eğer jQuery kullanıyorsanız, async parametresini false ayarlayabilirsiniz. Lütfen, jQuery 1.8’den bu yana bu seçeneğin kullanılmadığını unutmayın. Daha sonra success geri aramasını kullanabilir veya jqXHR nesnesinin responseText özelliğine erişebilirsiniz:

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

$.getJSON , $.getJSON vb. Gibi başka bir jQuery Ajax yöntemi $.getJSON , onu $.ajax değiştirmelisiniz (çünkü yapılandırma parametrelerini yalnızca $.ajax geçirebilirsiniz).

Dikkat et Eşzamanlı bir JSONP isteği yapılamıyor. JSONP doğası gereği her zaman eşzamansızdır (bu seçeneği göz önüne almamak için başka bir neden).

5011
08 янв. Cevap, Felix Kling tarafından 08 Ocak 2013-01-08 20:06 '13 20:06 2013-01-08 20:06

Kodunuzda jQuery kullanmıyorsanız, bu cevap tam size göre.

Kodunuz böyle bir şey olmalı:

 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, AJAX için jQuery kullanan kişilere cevap yazma konusunda harika bir iş yaptı, yapmayan insanlara bir alternatif sunmaya karar verdim.

( Yeni API fetch , Açısal veya söz fetch olanlar için aşağıya bir cevap daha eklediğimi lütfen unutmayın )


Ne karşılaştın

Bu, başka bir cevaptan "Sorunun Açıklaması" nın kısa bir özetidir, emin değilseniz, bunu okuduktan sonra bunu okuyun.

AJAX'taki bir asenkron demektir. Bu, normal bir uygulama akışından istek göndermenin (veya daha doğrusu bir yanıt almanın) kaldırılması anlamına gelir. Örnekte, .send derhal döndürülür ve bir sonraki deyim return result; geri arama olarak geçirdiğiniz işlevden önce çağrılır bile.

Bu, geri döndüğünüzde, belirttiğiniz dinleyicinin henüz yerine getirmediği, yani döndürdüğünüz değerin belirlenemediği anlamına gelir.

Basit analoji

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

(Fiddle)

a=5 kısmı henüz yürütülmediği için undefined dönüş değeri undefined . AJAX bunu yapar, sunucu tarayıcınıza değerin ne olduğunu söyleyemeden önce değeri döndürürsünüz.

Bu sorunun olası bir çözümü, kodu yeniden kullanmak ve hesaplama tamamlandığında ne yapılacağı hakkında programınıza bilgi vermektir.

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

Buna CPS denir. Temel olarak, getFive tamamlandığında gerçekleştirilmesi gereken eylemi veriyoruz getFive olay sona erdiğinde nasıl tepki vereceğimizi anlatıyoruz (örneğin, AJAX çağrımız veya bu durumda bir zaman aşımı).

kullanın:

 getFive(onComplete); 

Hangi ekranda "5" u uyarmalıdır. (Keman) .

Muhtemel çözümler

border=0

Bu sorunu çözmenin temelde iki yolu vardır:

  • Bir AJAX görüşmesini senkronize edin (onu SJAX olarak adlandırın).
  • Geri aramalarla doğru şekilde çalışacak şekilde kodunuzu yeniden yapılandırın.

1. Senkron AJAX - yapmayın !!

Senkron AJAX gelince, yapmayın! Felix’in tepkisi bunun neden kötü bir fikir olduğu konusunda bazı güçlü argümanlar veriyor. Özetlemek gerekirse, sunucu yanıt verene ve çok kötü bir kullanıcı arayüzü oluşturana kadar kullanıcının tarayıcısını dondurur. İşte neden başka bir MDN özeti:

XMLHttpRequest, hem senkron hem de asenkron iletişimi destekler. Bununla birlikte, genel olarak, zaman uyumsuz istekler, performans nedenlerinden dolayı senkronize istekler için tercih edilmelidir.

Kısacası, senkronize istekler kod yürütülmesini engeller ...... bu ciddi sorunlara neden olabilir ...

Bunu yapmanız gerekirse, bayrağını geçebilirsiniz: İşte nasıl:

 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. Yeniden Yapılandırma Kodu

İşleviniz geri aramayı kabul etsin. Örnekte foo kodu geri aramayı kabul etmek için yapılabilir. foo bittiğinde nasıl tepki vereceğimizi söyleyeceğiz.

Yani:

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

dönüşür:

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

Burada anonim bir işlevi geçtik, ancak varolan bir işleve bir bağlantı iletebiliriz, bu şekilde:

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

Bu geri arama türünün nasıl yürütüldüğü hakkında daha fazla bilgi için Felix yanıtını kontrol edin.

Şimdi buna göre hareket etmek için kendini tanımlayalım

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

(Keman)

Artık foo işlevimiz AJAX başarıyla tamamlandığında başlayan bir eylemi kabul ediyor, buna yanıt durumunun (200) yanıt verip vermediğini ve buna göre davranıp davranmadığını kontrol ederek devam edebiliriz (bir hata işleyicisi yarat vb.). Sorunumuza etkili bir çözüm.

Bunu anlamada hala sorun yaşıyorsanız , MDN'deki AJAX Baş> .

30 мая '13 в 2:30 2013-05-30 02:30 Tarafından verilen cevap Benjamin Gruenbaum tarafından 30 Mayıs '13, 02:30 2013-05-30 02:30

XMLHttpRequest 2 (Önce Benjamin Grünbaum ve Felix Kling'in cevaplarını okuyun)

Eğer jQuery kullanmıyorsanız ve modern tarayıcılarda ve mobil tarayıcılarda çalışan güzel bir kısa XMLHttpRequest 2 elde etmek istiyorsanız, aşağıdaki gibi kullanmanızı öneririm:

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

Gördüğünüz gibi:

  1. Listelenen diğer tüm işlevlerden daha kısa.
  2. Geri arama doğrudan kurulur (bu nedenle gereksiz gereksiz kapanmalar olmaz).
  3. XMLHttpRequest 1'i sinirlendiren, hatırlamadığım başka durumlar da var.

Bu Ajax çağrısına cevap almanın iki yolu vardır (üçü XMLHttpRequest değişken adını kullanarak):

En basit:

 this.response 

Veya, bir nedenden dolayı bir sınıfla geri aramayı bind() geri çağırıyorsanız:

 e.target.response 

örnek:

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

Veya (yukarıdaki daha iyi anonim işlevler her zaman bir sorundur):

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

Daha kolay bir şey yok.

Şimdi bazı insanlar muhtemelen onreadystatechange'i ya da XMLHttpRequest değişkeninin adını bile kullanmanın daha iyi olduğunu söyleyecekler. Bu yanlış.

XMLHttpRequest'in gelişmiş özelliklerini keşfedin

Tüm modern tarayıcılar desteklenir. Ve bu yaklaşımı kullandığımı doğrulayabilirim çünkü XMLHttpRequest 2 var. Kullandığım tüm tarayıcılarda hiç sorun yaşamadım.

onreadystatechange yalnızca başlıkları 2 durumuna almak istiyorsanız kullanışlıdır.

XMLHttpRequest değişken adını kullanmak başka bir büyük hatadır, çünkü aşırı yükleme / oreadystatechange kapanışları içinde geri aramanız gerekir, aksi halde onu kaybettiniz.


Şimdi, post ve FormData kullanarak daha karmaşık bir şey istiyorsanız, bu işlevi kolayca genişletebilirsiniz:

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

Yine ... bu çok kısa bir işlevdir, ancak alır ve yayınlar.

Kullanım örnekleri:

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

Veya formun tamamını ( document.getElementsByTagName('form')[0] ) iletin:

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

Veya bazı özel değerler ayarlayın:

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

Gördüğünüz gibi senkronizasyon yapmadım ... bu kötü.

Bunu söyledikten sonra ... neden basit bir şekilde yapmıyorsun?


Yorumda belirtildiği gibi, senkronize hata kullanımı tamamen cevabın anlamını ihlal ediyor. Ajax'ı doğru kullanmak için iyi bir kısa yol nedir?

Hata işleyicisi

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

Yukarıdaki komut dosyasında, statik olarak tanımlanmış bir hata işleyiciniz vardır, bu nedenle işlevden ödün vermez. Hata işleyicisi diğer işlevler için kullanılabilir.

Ancak gerçekten hatayı almak için tek yol yanlış URL'yi yazmaktır, bu durumda her tarayıcı bir hata verir.

Hata işleyicileri, özel başlıklar ayarladıysanız, bir blob dizi arabelleği ya da başka bir şey için yanıt türünü ayarlarsanız yararlı olabilir ...

POSTAPAPAP'ı bir yöntem olarak geçseniz bile, bir hata vermez.