Eşzamansız bir aramadan gelen yanıtı nasıl döndürürüm?
Ben bir işlevi var foo
uyumsuz bir istekte bulunur. Yanıtı / sonucu nasıl iade edebilirim foo
?
Geri aramadan değeri döndürmeyi denedim, aynı zamanda sonucu işlevin içindeki yerel bir değişkene atamayı ve onu döndürmeyi denedim, ancak bu yollardan hiçbiri aslında yanıtı döndürmüyor (hepsi dönüyor undefined
veya değişkenin başlangıç değeri ne olursa olsun result
) .
JQuery ajax
işlevini kullanan örnek :
function foo() {
var result;
$.ajax({
url: '...',
success: function(response) {
result = response;
// return response; // <- I tried that one as well
}
});
return result; // It always returns `undefined`
}
Node.js kullanma örneği:
function foo() {
var result;
fs.readFile("path/to/file", function(err, data) {
result = data;
// return data; // <- I tried that one as well
});
return result; // It always returns `undefined`
}
then
Bir söz bloğunun kullanıldığı örnek :
function foo() {
var result;
fetch(url).then(function(response) {
result = response;
// return response; // <- I tried that one as well
});
return result; // It always returns `undefined`
}
Yanıtlar
→ Farklı örneklerle zaman uyumsuz davranışın daha genel bir açıklaması için, lütfen değişkenim bir işlevin içinde değiştirdikten sonra neden değiştirilmedi? Konusuna bakın. - Eşzamansız kod referansı
→ Sorunu zaten anladıysanız, aşağıdaki olası çözümlere geçin.
Sorun
Bir de Ajax açılımı asenkron . Bu, isteğin gönderilmesi (veya yanıtı almanın) normal yürütme akışından çıkarıldığı anlamına gelir. Örneğinizde, $.ajax
hemen döner ve bir sonraki ifade, geri arama olarak return result;
geçirdiğiniz işlev success
çağrılmadan önce çalıştırılır .
Eşzamanlı ve eşzamansız akış arasındaki farkı daha net hale getirmesini umduğumuz bir benzetme:
Senkron
Bir arkadaşınızla bir telefon görüşmesi yaptığınızı ve ondan sizin için bir şeyler aramasını istediğinizi hayal edin. Biraz zaman alsa da, arkadaşınız size ihtiyacınız olan cevabı verene kadar telefonda beklersiniz ve boşluğa bakarsınız.
Aynı şey, "normal" kod içeren bir işlev çağrısı yaptığınızda da olur:
function findItem() {
var item;
while(item_not_found) {
// search
}
return item;
}
var item = findItem();
// Do something with item
doSomethingElse();
Yürütülmesi findItem
uzun zaman alsa da, sonrasında gelen herhangi bir kod , işlevin sonucu döndürmesini beklemekvar item = findItem();
zorundadır .
Eşzamansız
Arkadaşını yine aynı sebepten arıyorsun. Ama bu sefer ona aceleniz olduğunu ve sizi cep telefonunuzdan araması gerektiğini söylüyorsunuz . Telefonu kapatırsın, evi terk edersin ve yapmayı planladığın her şeyi yaparsın. Arkadaşınız sizi geri aradığında, size verdiği bilgilerle ilgileniyorsunuz.
Ajax isteği yaptığınızda olan tam olarak budur.
findItem(function(item) {
// Do something with the item
});
doSomethingElse();
Yanıtı beklemek yerine, yürütme hemen devam eder ve Ajax çağrısının ardından ifade çalıştırılır. Sonunda yanıtı almak için, yanıt alındığında çağrılacak bir işlev, bir geri arama (bir şey fark ettiniz mi? Geri aramak mı?) Sağlarsınız . Bu aramadan sonra gelen herhangi bir ifade, geri arama çağrılmadan önce yürütülür.
Çözüm (ler)
JavaScript'in eşzamansız doğasını kucaklayın! Bazı eşzamansız işlemler eşzamanlı emsaller sağlasa da ("Ajax" da öyle), bunları özellikle tarayıcı bağlamında kullanmak genellikle tavsiye edilmez.
Neden soruyorsun kötü?
JavaScript, tarayıcının UI iş parçacığında çalışır ve uzun süren herhangi bir işlem UI'yi kilitleyerek yanıt vermemesine neden olur. Ek olarak, JavaScript için yürütme süresinde bir üst sınır vardır ve tarayıcı, kullanıcıya yürütmeye devam edip etmeyeceğini sorar.
Bunların hepsi gerçekten kötü bir kullanıcı deneyimi. Kullanıcı her şeyin yolunda gidip gitmediğini anlayamayacak. Ayrıca, bağlantısı yavaş olan kullanıcılar için etki daha kötü olacaktır.
Aşağıda, hepsi birbirinin üzerine inşa edilen üç farklı çözüme bakacağız:
- İle Vaatler
async/await
(bir transpiler veya rejeneratör kullanırsanız eski tarayıcılarda kullanılabilir ES2017 +,) - Geri aramalar (düğümde popüler)
then()
(ES2015 +, vaat edilen birçok kitaplıktan birini kullanıyorsanız eski tarayıcılarda mevcuttur) ile vaatler
Üçü de mevcut tarayıcılarda ve 7+ düğümünde mevcuttur.
ES2017 +: Vaatler async/await
2017'de yayınlanan ECMAScript sürümü , eşzamansız işlevler için sözdizimi düzeyinde destek sunmuştur. Yardımıyla async
ve await
bir "senkron tarzı" asenkron yazabilir. Kod hala eşzamansızdır, ancak okunması / anlaşılması daha kolaydır.
async/await
vaatlerin üzerine inşa edilir: bir async
işlev her zaman bir söz verir. await
Bir sözü "çözer" ve sözün çözüldüğü değerle sonuçlanır veya söz reddedilirse bir hata verir.
Önemli: Yalnızca await
bir async
işlevin içinde kullanabilirsiniz . Şu anda üst düzey await
henüz desteklenmiyor, bu nedenle bir bağlam başlatmak için bir asenkron IIFE ( Anında Çağrılan İşlev İfadesi ) yapmanız gerekebilir async
.
MDN hakkında asyncve awaitMDN hakkında daha fazla bilgi edinebilirsiniz .
İşte yukarıdaki gecikmenin üzerine inşa edilen 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);
})();
Mevcut tarayıcı ve düğüm sürümleri desteklenir async/await
. Rejeneratör (ya da Babel gibi rejeneratör kullanan araçlar) yardımıyla kodunuzu ES5'e dönüştürerek eski ortamları da destekleyebilirsiniz .
İşlevlerin geri aramaları kabul etmesine izin verin
Geri arama, işlev 1'in işlev 2'ye geçmesidir. İşlev 2, hazır olduğunda işlev 1'i çağırabilir. Eşzamansız bir işlem bağlamında, eşzamansız işlem her yapıldığında geri arama çağrılacaktır. Genellikle sonuç geri aramaya aktarılır.
Soru örneğinde, foo
bir geri aramayı kabul edebilir ve onu success
geri arama olarak kullanabilirsiniz . Yani bu
var result = foo();
// Code that depends on 'result'
olur
foo(function(result) {
// Code that depends on 'result'
});
Burada "satır içi" işlevini tanımladık, ancak herhangi bir işlev başvurusunu iletebilirsiniz:
function myCallback(result) {
// Code that depends on 'result'
}
foo(myCallback);
foo
kendisi şu şekilde tanımlanır:
function foo(callback) {
$.ajax({
// ...
success: callback
});
}
callback
foo
onu çağırdığımızda geçtiğimiz işleve atıfta bulunacak ve onu aktaracağız success
. Yani Ajax isteği başarılı olduğunda $.ajax
, arar callback
ve yanıtı geri aramaya iletir (geri aramayı result
böyle tanımladığımız için buna başvurulabilir ).
Yanıtı geri aramaya iletmeden ö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 büyük ölçüde olay odaklıdır (DOM olayları). Ajax yanıtını almak bir olaydan başka bir şey değildir.
Üçüncü taraf kodla çalışmanız gerektiğinde zorluklar ortaya çıkabilir, ancak çoğu sorun yalnızca uygulama akışını düşünerek çözülebilir.
ES2015 +: O zaman vaat ediyor ()
Promise API ECMAScript'e 6 (ES2015) yeni bir özelliktir, ama iyi vardır tarayıcı desteği zaten. Ayrıca standart Promises API'yi uygulayan ve asenkron işlevlerin (örn. Bluebird ) kullanımını ve bileşimini kolaylaştırmak için ek yöntemler sağlayan birçok kitaplık vardır .
Sözler, gelecekteki değerlerin kaplarıdır . Söz, değeri aldığında ( çözüldüğünde ) veya iptal edildiğinde ( reddedildiğinde ), bu değere erişmek isteyen tüm "dinleyicilerine" bildirimde bulunur.
Düz geri aramalara göre avantajı, kodunuzu ayırmanıza izin vermeleri ve oluşturmanın daha kolay olmasıdır.
İşte bir söz kullanma ö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ıza uygulandığında, aşağıdaki gibi vaatleri kullanabilirdik:
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
});
Teklif vaat eden tüm avantajları anlatmak bu cevabın kapsamı dışındadır, ancak yeni bir kod yazarsanız, bunları ciddiye almalısınız. Kodunuzun büyük bir soyutlamasını ve ayrılmasını sağlarlar.
Vaatler hakkında daha fazla bilgi: HTML5 rock - JavaScript Promises
Yan not: jQuery'nin ertelenmiş nesneleri
Ertelenen nesneler , jQuery'nin özel vaat uygulamasıdır (Promise API standartlaştırılmadan önce). Neredeyse vaatler gibi davranırlar, ancak biraz farklı bir API ortaya çıkarırlar.
JQuery'nin her Ajax yöntemi, işlevinizden geri dönebileceğiniz bir "ertelenmiş nesne" (aslında ertelenmiş bir nesnenin vaadi) döndürür:
function ajax() {
return $.ajax(...);
}
ajax().done(function(result) {
// Code depending on result
}).fail(function() {
// An error occurred
});
Yan not: Promise gotchas
Vaatlerin ve ertelenen nesnelerin yalnızca gelecekteki bir değer için kaplar olduğunu, değerin kendisi olmadığını 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 eşzamansızlık sorunlarını yanlış anlar. Özellikle, $.ajax()
sunucunuzdaki '/ password' sayfasını kontrol ederken kodu dondurmaz - sunucuya bir istek gönderir ve beklerken, sunucudan gelen yanıtı değil, hemen bir jQuery Ajax Ertelenmiş nesnesi döndürür. Bu, if
ifadenin her zaman bu Ertelenmiş nesneyi alacağı, ona davranacağı true
ve kullanıcı oturum açmış gibi devam edeceği anlamına gelir . İyi değil.
Ancak düzeltmek kolaydır:
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
});
Önerilmez: Eşzamanlı "Ajax" çağrıları
Bahsettiğim gibi, bazı (!) Eşzamansız işlemlerin eşzamanlı karşılıkları vardır. Kullanımlarını savunmuyorum, ancak bütünlük adına, işte eşzamanlı bir aramayı şu şekilde gerçekleştirirsiniz:
JQuery olmadan
Doğrudan bir XMLHttpRequestnesne kullanıyorsanız, false
üçüncü argüman olarak iletin .open.
jQuery
JQuery kullanıyorsanız , async
seçeneği olarak ayarlayabilirsiniz false
. Bu seçeneğin jQuery 1.8'den beri kullanımdan kaldırıldığını unutmayın . Daha sonra bir success
geri aramayı kullanmaya devam edebilir veya jqXHR nesnesininresponseText
özelliğine erişebilirsiniz :
function foo() {
var jqXHR = $.ajax({
//...
async: false
});
return jqXHR.responseText;
}
Başka jQuery Ajax yöntemi gibi kullanırsanız $.get
, $.getJSON
vb, aşağıda belirtilen yerlere değiştirmek zorunda $.ajax
(sadece yapılandırma parametreleri geçirebilirsiniz beri $.ajax
).
Önüne bak! Eşzamanlı bir JSONP isteği yapmak mümkün değildir . JSONP doğası gereği her zaman eşzamansızdır (bu seçeneği dikkate almamak için bir neden daha).
Eğer ediyorsanız değil kodunuzda jQuery kullanarak, bu cevap size göre
Kodunuz şu satırlarda olmalıdır:
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şiler için bir cevap yazarak iyi bir iş çıkardı, olmayan insanlar için bir alternatif sağlamaya karar verdim.
( Not, yeni fetchAPI, Angular veya sözler kullananlar için aşağıya başka bir cevap ekledim )
Neyle yüzleşiyorsun
Bu diğer cevaptan "Sorunun açıklaması" nın kısa bir özetidir, bunu okuduktan sonra emin değilseniz okuyun.
Bir AJAX açılımı asenkron . Bu, isteğin gönderilmesi (veya yanıtı almanın) normal yürütme akışından çıkarıldığı anlamına gelir. Örneğinizde, .sendhemen döner ve bir sonraki ifade, geri arama olarak return result;
geçirdiğiniz işlev success
çağrılmadan önce çalıştırılır .
Bu, geri döndüğünüzde, tanımladığınız dinleyicinin henüz çalışmadığı anlamına gelir, bu, döndürdüğünüz değerin tanımlanmadığı anlamına gelir.
İşte basit bir benzetme
function getFive(){
var a;
setTimeout(function(){
a=5;
},10);
return a;
}
(Vaktini boşa harcamak)
a
Döndürülen değer undefined
, a=5
parça henüz çalıştırılmadığından beri . AJAX bu şekilde davranır, sunucu tarayıcınıza bu değerin ne olduğunu söyleme şansı vermeden önce değeri döndürürsünüz.
Bu soruna olası bir çözüm , programınıza hesaplama tamamlandığında ne yapacağını söyleyerek yeniden kodlama yapmaktır.
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ştirilecek bir eylemi iletiyoruz, kodumuza bir olay tamamlandığında nasıl tepki vereceğini söylüyoruz (AJAX çağrımız veya bu durumda zaman aşımı gibi).
Kullanım şöyle olacaktır:
getFive(onComplete);
Hangi ekrana "5" uyarısı vermelidir. (Keman) .
Muhtemel çözümler
Bunu çözmenin temelde iki yolu vardır:
- AJAX çağrısını eşzamanlı yapın (buna SJAX diyelim).
- Geri aramalarla düzgün çalışması için kodunuzu yeniden yapılandırın.
1. Eşzamanlı AJAX - Yapmayın !!
Senkronize AJAX'a gelince, bunu yapmayın! Felix'in cevabı, bunun neden kötü bir fikir olduğu konusunda bazı ikna edici argümanlar doğurur. Özetlemek gerekirse, sunucu yanıtı döndürene ve çok kötü bir kullanıcı deneyimi yaratana kadar kullanıcının tarayıcısını dondurur. İşte MDN'den neden olduğuna dair başka bir kısa özet:
XMLHttpRequest hem eşzamanlı hem de eşzamansız iletişimleri destekler. Ancak genel olarak, performans nedenlerinden ötürü zaman uyumsuz istekler, eşzamanlı isteklere tercih edilmelidir.
Kısacası, senkronize istekler kodun yürütülmesini engeller ... ... bu ciddi sorunlara neden olabilir ...
Eğer varsa sahip bunu yapmak için, bir bayrak geçirebilirsiniz: Bu şekilde yapılabilir:
var request = new XMLHttpRequest();
request.open('GET', 'yourURL', false); // `false` makes the request synchronous
request.send(null);
if (request.status === 200) {// That's HTTP for 'ok'
console.log(request.responseText);
}
2. Kodu yeniden yapılandırın
Fonksiyonunuzun bir geri aramayı kabul etmesine izin verin. Örnekte, foo
bir geri aramayı kabul etmek için kod yapılabilir. Biz nasıl bizim kod söylüyorum olacak tepki zaman foo
tamamlar.
Yani:
var result = foo();
// code that depends on `result` goes here
Olur:
foo(function(result) {
// code that depends on `result`
});
Burada anonim bir işlevi geçtik, ancak mevcut bir işleve bir referansı da aynı şekilde kolayca iletebilir ve şöyle görünmesini sağlayabiliriz:
function myHandler(result) {
// code that depends on `result`
}
foo(myHandler);
Bu tür bir geri arama tasarımının nasıl yapıldığına dair daha fazla ayrıntı için Felix'in cevabına bakın.
Şimdi, buna göre hareket etmek için foo'nun kendisini 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();
}
(Vaktini boşa harcamak)
Şimdi foo fonksiyonumuzun AJAX başarıyla tamamlandığında çalışacak bir eylemi kabul etmesini sağladık, yanıt durumunun 200 olup olmadığını kontrol ederek ve buna göre hareket ederek (bir hata işleyicisi vb. Oluşturun) bunu daha da genişletebiliriz. Sorunumuzu etkili bir şekilde çözüyoruz.
Hala bunu anlamakta güçlük çekiyorsanız , MDN'deki AJAX başlangıç kılavuzunu okuyun .
XMLHttpRequest 2 (öncelikle Benjamin Gruenbaum ve Felix Kling'in yanıtlarını okuyun)
JQuery kullanmıyorsanız ve modern tarayıcılarda ve ayrıca mobil tarayıcılarda çalışan güzel ve kısa bir XMLHttpRequest 2 istiyorsanız, şu şekilde 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 gibi:
- Listelenen diğer tüm işlevlerden daha kısadır.
- Geri arama doğrudan ayarlanır (bu nedenle fazladan gereksiz kapatmalar olmaz).
- Yeni onload'u kullanır (böylece readystate && durumunu kontrol etmeniz gerekmez)
- XMLHttpRequest 1'i sinir bozucu yapan hatırlamadığım başka durumlar da var.
Bu Ajax çağrısının yanıtını almanın iki yolu vardır (XMLHttpRequest değişken adını kullanan üç):
En basit:
this.response
Veya herhangi bir nedenle bind()
bir sınıfa geri ararsanız:
e.target.response
Misal:
function callback(e){
console.log(this.response);
}
ajax('URL', callback);
Veya (yukarıdakinin daha iyi anonim işlevler her zaman bir sorundur):
ajax('URL', function(e){console.log(this.response)});
Daha kolay değil.
Şimdi bazı insanlar muhtemelen onreadystatechange veya hatta XMLHttpRequest değişken adını kullanmanın daha iyi olduğunu söyleyecektir. Bu yanlış.
XMLHttpRequest gelişmiş özelliklerine göz atın
Tüm * modern tarayıcıları destekledi. Ve XMLHttpRequest 2 var olduğu için bu yaklaşımı kullandığımı doğrulayabilirim. Kullandığım tüm tarayıcılarda hiç sorun yaşamadım.
onreadystatechange, yalnızca 2. durumdaki başlıkları almak istiyorsanız kullanışlıdır.
XMLHttpRequest
Değişken adını kullanmak başka bir büyük hatadır, çünkü onu kaybettiğinizde onload / oreadystatechange kapanışları içinde geri aramayı yürütmeniz gerekir.
Ş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şlev, ancak alıyor ve gönderiyor.
Kullanım örnekleri:
x(url, callback); // By default it's get so no need to set
x(url, callback, 'post', {'key': 'val'}); // No need to set post data
Veya bir tam form öğesi ( 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 senkronizasyonu uygulamadım ... bu kötü bir şey.
Bunu söyledikten sonra ... neden bunu kolay yoldan yapmıyorsunuz?
Yorumda belirtildiği gibi, error && synchronous kullanımı cevabın ana fikrini tamamen bozar. Ajax'ı doğru şekilde kullanmanın güzel ve kısa yolu hangisidir?
Hata işleyici
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, işlevi tehlikeye atmaması için statik olarak tanımlanmış bir hata işleyiciniz vardır. Hata işleyici diğer işlevler için de kullanılabilir.
Ancak bir hatadan gerçekten kurtulmanın tek yolu yanlış bir URL yazmaktır, bu durumda her tarayıcı bir hata atar.
Hata işleyicileri, özel başlıklar ayarlarsanız, responseType değerini blob dizi arabelleğine ayarlarsanız veya her neyse yararlı olabilir ...
Yöntem olarak 'POSTAPAPAP'ı geçseniz bile, bir hata vermez.
"Fdggdgilfdghfldj" yi formdata olarak geçirseniz bile bir hata vermez.
İlk durumda hata içindedir displayAjax()
altındakiler this.statusText
olarak Method not Allowed
.
İkinci durumda, basitçe çalışır. Doğru gönderi verilerini geçip geçmediğinizi sunucu tarafında kontrol etmelisiniz.
alanlar arası izin verilmiyor hata otomatik olarak atar.
Hata yanıtında herhangi bir hata kodu yoktur.
Yalnızca this.type
hataya ayarlanmış olan vardır.
Hatalar üzerinde hiçbir kontrolünüz yoksa neden bir hata işleyici eklemelisiniz? Hataların çoğu, geri arama işlevinde bunun içinde döndürülür displayAjax()
.
Yani: URL'yi düzgün bir şekilde kopyalayıp yapıştırabiliyorsanız, hata kontrollerine gerek yoktur. ;)
Not: İlk test olarak x ('x', displayAjax) yazdım ... ve tamamen bir yanıt aldı ... ??? Bu yüzden HTML'nin bulunduğu klasörü kontrol ettim ve 'x.xml' adlı bir dosya vardı. Yani XMLHttpRequest 2 dosyanızın uzantısını unutsanız bile BUNU BULACAKTIR . Ben LOL'ledim
Eşzamanlı bir dosya okuyun
Bunu yapma.
Tarayıcıyı bir süreliğine bloke etmek istiyorsanız, .txt
senkronize güzel bir büyük dosya yükleyin .
function omg(a, c){ // URL
c = new XMLHttpRequest;
c.open('GET', a, true);
c.send();
return c; // Or c.response
}
Şimdi yapabilirsin
var res = omg('thisIsGonnaBlockThePage.txt');
Bunu eşzamansız bir şekilde yapmanın başka bir yolu yoktur. (Evet, setTimeout döngüsüyle ... ama cidden mi?)
Diğer bir nokta ise ... API'lerle veya sadece kendi listenizin dosyalarıyla çalışıyorsanız veya her istek için her zaman farklı işlevleri kullanırsanız ...
Yalnızca, her zaman aynı XML / JSON'u veya yalnızca bir işleve ihtiyacınız olan her şeyi yüklediğiniz bir sayfanız varsa. Bu durumda, Ajax işlevini biraz değiştirin ve b'yi özel işlevinizle değiştirin.
Yukarıdaki işlevler temel kullanım içindir.
İşlevi GENİŞLETMEK istiyorsanız ...
Evet yapabilirsin.
Çok sayıda API kullanıyorum ve her HTML sayfasına entegre ettiğim ilk işlevlerden biri, bu yanıttaki ilk Ajax işlevi, yalnızca GET ile ...
Ancak XMLHttpRequest 2 ile pek çok şey yapabilirsiniz:
Bir indirme yöneticisi yaptım (her iki tarafta özgeçmiş, dosya okuyucusu, dosya sistemi ile aralıklar kullanarak), tuval kullanarak çeşitli görüntü yeniden boyutlandırıcı dönüştürücüler, base64 görüntülerle web SQL veritabanlarını doldurma ve çok daha fazlası ... Ancak bu durumlarda yalnızca bunun için bir işlev oluşturmalısınız amaç ... bazen bir blob'a, dizi tamponlarına ihtiyaç duyarsınız, başlıkları ayarlayabilir, mime tipini geçersiz kılabilirsiniz ve çok daha fazlası vardır ...
Ancak buradaki soru, bir Ajax yanıtının nasıl döndürüleceğidir ... (Kolay bir yol ekledim.)
Söz kullanıyorsanız, bu cevap tam size göre.
Bu, AngularJS, jQuery (ertelenmiş), yerel XHR'nin değişimi (getirme), EmberJS, BackboneJS'nin kaydetme veya vaatleri döndüren herhangi bir düğüm kitaplığı anlamına gelir.
Kodunuz şu satırlarda olmalıdır:
function foo() {
var data;
// or $.get(...).then, or request(...).then, or query(...).then
fetch("/echo/json").then(function(response){
data = response.json();
});
return data;
}
var result = foo(); // result is always undefined no matter what.
Felix Kling, AJAX için geri aramalarla jQuery kullanan kişiler için bir cevap yazmakta iyi bir iş çıkardı. Yerel XHR için bir cevabım var. Bu cevap, ön uçta veya arka uçta vaatlerin genel kullanımı içindir.
Temel sorun
NodeJS / io.js ile tarayıcıdaki ve sunucudaki JavaScript eşzamanlılık modeli eşzamansız ve reaktiftir .
Bir promise döndüren bir yöntemi her çağırdığınızda, then
işleyiciler her zaman eşzamansız olarak, yani bir işleyicide olmayan altlarındaki koddan sonra çalıştırılır .then
.
Bu araçlar size iade ederken henüz yürütülmediği tanımladığınız işleyicisi. Bu da, döndürdüğünüz değerin zaman içinde doğru değere ayarlanmadığı anlamına gelir.data
then
İşte sorun için basit bir benzetme:
function getFive(){
var data;
setTimeout(function(){ // set a timer for one second in the future
data = 5; // after a second, do this
}, 1000);
return data;
}
document.body.innerHTML = getFive(); // `undefined` here and not 5
Değeri data
olan undefined
beri data = 5
parçası henüz infaz edilmemiştir. Muhtemelen bir saniyede çalıştırılacak, ancak o zamana kadar döndürülen değerle ilgisi yok.
İşlem henüz gerçekleşmediğinden (AJAX, sunucu çağrısı, IO, zamanlayıcı), istek kodunuza bu değerin ne olduğunu söyleme şansı vermeden önce değeri döndürüyorsunuz.
Bu soruna olası bir çözüm , programınıza hesaplama tamamlandığında ne yapacağını söyleyerek yeniden kodlama yapmaktır. Vaatler, doğası gereği zamansal (zamana duyarlı) olarak bunu aktif olarak sağlar.
Sözlerin hızlı bir özeti
Söz, zaman içindeki bir değerdir . Vaatlerin durumu vardır, hiçbir değeri olmayan beklemede olarak başlarlar ve şu noktalara yerleşebilirler:
- yerine getirildi , hesaplamanın başarıyla tamamlandığı anlamına gelir.
- reddedildi , hesaplamanın başarısız olduğu anlamına gelir.
Bir vaat, durumları yalnızca bir kez değiştirebilir ve bundan sonra daima aynı durumda kalır. then
Değerlerini çıkarmak ve hataları işlemek için vaatlere işleyiciler ekleyebilirsiniz . then
işleyiciler aramaların zincirlenmesine izin verir . Sözler, onları döndüren API'ler kullanılarak oluşturulur . Örneğin, daha modern AJAX değişimi fetch
veya jQuery'nin $.get
iade vaatleri.
Bir .then
söze çağırdığımızda ve ondan bir şey iade ettiğimizde - işlenmiş değer için bir söz alırız . Başka bir söz verirsek harika şeyler elde ederiz ama atlarımızı tutalım.
Sözlerle
Yukarıdaki sorunu vaatlerle nasıl çözebiliriz bir bakalım. Öncelikle, bir delay işlevi oluşturmak için Promise yapıcısını kullanarak yukarıdan vaat durumları anlayışımızı gösterelim :
function delay(ms){ // takes amount of milliseconds
// returns a new promise
return new Promise(function(resolve, reject){
setTimeout(function(){ // when the time is up
resolve(); // change the promise to the fulfilled state
}, ms);
});
}
Şimdi, setTimeout'u vaatleri kullanacak şekilde dönüştürdükten sonra, then
onu saymak için kullanabiliriz :
function delay(ms){ // takes amount of milliseconds
// returns a new promise
return new Promise(function(resolve, reject){
setTimeout(function(){ // when the time is up
resolve(); // change the promise to the fulfilled state
}, ms);
});
}
function getFive(){
// we're RETURNING the promise, remember, a promise is a wrapper over our value
return delay(100).then(function(){ // when the promise is ready
return 5; // return the value 5, promises are all about return values
})
}
// we _have_ to wrap it like this in the call site, we can't access the plain value
getFive().then(function(five){
document.body.innerHTML = five;
});
Temel olarak, bir dönen yerine değerini biz çünkü eşzamanlılık modeli yapamaz - bir geri dönüyoruz sarmalayıcı biz ki bir değeri paketini ile then
. Birlikte açabileceğiniz bir kutu gibi then
.
Bunu uyguluyorum
Bu, orijinal API çağrınız için aynıdır, şunları yapabilirsiniz:
function foo() {
// RETURN the promise
return fetch("/echo/json").then(function(response){
return response.json(); // process it inside the `then`
});
}
foo().then(function(response){
// access the value inside the `then`
})
Yani bu da aynı şekilde çalışıyor. Zaten eşzamansız çağrılardan değer döndüremeyeceğimizi öğrendik, ancak işlemleri gerçekleştirmek için sözler kullanabilir ve bunları zincirleyebiliriz. Artık eşzamansız bir aramadan gelen yanıtı nasıl döndüreceğimizi biliyoruz.
ES2015 (ES6)
ES6 tanıtır jeneratörler orta dönmek ve sonra da vardı noktasını devam edebilirsiniz fonksiyonlardır. Bu tipik olarak diziler için kullanışlıdır, örneğin:
function* foo(){ // notice the star, this is ES6 so new browsers/node/io only
yield 1;
yield 2;
while(true) yield 3;
}
Yinelenebilen dizi üzerinde bir yineleyici döndüren bir işlevdir 1,2,3,3,3,3,....
. Bu kendi başına ilginç ve birçok olasılığa yer açsa da, belirli bir ilginç durum var.
Ürettiğimiz dizi sayılardan ziyade bir eylemler dizisiyse - bir eylem verildiğinde işlevi duraklatabilir ve işleve devam etmeden önce bekleyebiliriz. Dolayısıyla, bir dizi sayı yerine, bir dizi gelecekteki değerlere ihtiyacımız var - yani: vaatler.
Bu biraz zor ama çok güçlü numara, eşzamansız kodu eşzamanlı bir şekilde yazmamızı sağlar. Bunu sizin için yapan birkaç "koşucu" vardır, birini yazmak kısa birkaç satırlık koddur, ancak bu cevabın kapsamı dışındadır. Bluebird'leri Promise.coroutine
burada kullanacağım , ancak co
veya gibi başka sarmalayıcılar da var Q.async
.
var foo = coroutine(function*(){
var data = yield fetch("/echo/json"); // notice the yield
// code here only executes _after_ the request is done
return data.json(); // data is defined
});
Bu yöntem, diğer coroutinlerden tüketebileceğimiz bir vaadi kendisi döndürür. Örneğin:
var main = coroutine(function*(){
var bar = yield foo(); // wait our earlier coroutine, it returns a promise
// server call done here, code below executes when done
var baz = yield fetch("/api/users/"+bar.userid); // depends on foo's result
console.log(baz); // runs after both requests done
});
main();
ES2016 (ES7)
ES7'de bu daha da standartlaştırılmıştır, şu anda birkaç teklif var ama hepsinde await
söz verebilirsiniz . Bu, yukarıdaki ES6 önerisi için async
ve await
anahtar kelimelerini ekleyerek "şeker" (daha güzel sözdizimi) şeklindedir . Yukarıdaki örneği yapmak:
async function foo(){
var data = await fetch("/echo/json"); // notice the await
// code here only executes _after_ the request is done
return data.json(); // data is defined
}
Yine de aynı şekilde bir söz verir :)
Ajax'ı yanlış kullanıyorsunuz. Buradaki fikir, herhangi bir şey döndürmesini sağlamak değil, bunun yerine verileri, verileri işleyen geri arama işlevi adı verilen bir şeye devretmektir.
Yani:
function handleData( responseData ) {
// Do what you want with the data
console.log(responseData);
}
$.ajax({
url: "hi.php",
...
success: function ( data, status, XHR ) {
handleData(data);
}
});
Gönderme işleyicisindeki herhangi bir şeyi döndürmek hiçbir şey yapmaz. Bunun yerine ya veriyi devretmeli ya da doğrudan başarı işlevi içinde verilerle istediğinizi yapmalısınız.
En basit çözüm, bir JavaScript işlevi oluşturmak ve onu Ajax success
geri araması için çağırmaktır .
function callServerAsync(){
$.ajax({ url: '...', success: function(response) { successCallback(response); } }); } function successCallback(responseObj){ // Do something like read the response and show data alert(JSON.stringify(responseObj)); // Only applicable to JSON response } function foo(callback) { $.ajax({
url: '...',
success: function(response) {
return callback(null, response);
}
});
}
var result = foo(function(err, result){
if (!err)
console.log(result);
});
Korkunç görünümlü, elle çizilmiş bir çizgi romanla cevaplayacağım. İkinci resim nedeni budur result
olduğunu undefined
sizin kod örneğinde.
Angular1
AngularJS kullanan kişiler için bu durumu kullanarak halledebilir Promises
.
İşte diyor ki,
Sözler, eşzamansız işlevleri ayırmak için kullanılabilir ve birden çok işlevi birlikte zincirlemeye izin verir.
Burada da güzel bir açıklama bulabilirsiniz .
Aşağıda belirtilen belgelerde bulunan örnek .
promiseB = promiseA.then(
function onSuccess(result) {
return result + 1;
}
,function onError(err) {
//Handle error
}
);
// promiseB will be resolved immediately after promiseA is resolved
// and its value will be the result of promiseA incremented by 1.
Angular2 ve Daha Sonra
In Angular2
aşağıdaki örnekte bakmak, ama onun ile önerilen kullanımına Observables
sahip Angular2
.
search(term: string) {
return this.http
.get(`https://api.spotify.com/v1/search?q=${term}&type=artist`)
.map((response) => response.json())
.toPromise();
}
Bunu bu şekilde tüketebilirsiniz,
search() {
this.searchService.search(this.searchField.value)
.then((result) => {
this.result = result.artists.items;
})
.catch((error) => console.error(error));
}
Orijinal gönderiye buradan bakın . Ancak Typescript yerel es6 Promises'i desteklemez , eğer onu kullanmak istiyorsanız, bunun için eklentiye ihtiyacınız olabilir.
Ayrıca burada söz olduğunu Spec burada tanımlar.
Buradaki yanıtların çoğu, tek bir eşzamansız işleminiz olduğunda yararlı öneriler sunar, ancak bazen bu , bir dizideki veya başka bir liste benzeri yapıdaki her giriş için eşzamansız bir işlem yapmanız gerektiğinde ortaya çıkar . Bunu yapmak için cazip olan şey:
// WRONG
var results = [];
theArray.forEach(function(entry) {
doSomethingAsync(entry, function(result) {
results.push(result);
});
});
console.log(results); // E.g., using them, returning them, etc.
Misal:
// WRONG
var theArray = [1, 2, 3];
var results = [];
theArray.forEach(function(entry) {
doSomethingAsync(entry, function(result) {
results.push(result);
});
});
console.log("Results:", results); // E.g., using them, returning them, etc.
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
Bunun işe yaramamasının nedeni doSomethingAsync
, sonuçları kullanmaya çalıştığınız zamana kadar gelen geri aramaların henüz çalışmamış olmasıdır .
Dolayısıyla, bir diziniz (veya bir tür listeniz) varsa ve her giriş için eşzamansız işlemler yapmak istiyorsanız, iki seçeneğiniz vardır: İşlemleri paralel (örtüşen) veya seri (sırayla birbiri ardına) yapın.
Paralel
Hepsini başlatabilir ve kaç geri arama beklediğinizi takip edebilir ve ardından bu kadar çok geri arama aldığınızda sonuçları kullanabilirsiniz:
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
console.log("Results:", results); // E.g., using the results
}
});
});
Misal:
var theArray = [1, 2, 3];
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
console.log("Results:", results); // E.g., using the results
}
});
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
(Bunu ortadan kaldırabilir expecting
ve sadece kullanabilirdik results.length === theArray.length
, ancak bu bizi theArray
aramalar beklemedeyken değişen olasılığa açık bırakır ...)
Kullandığımız nasıl Bildirimi index
dan forEach
sonucu kurtarmak için results
sonuçlar sırasız gelen olsa bile, bu ilgilidir girdi olarak aynı pozisyonda (zaman uyumsuz aramalar sırayla tamamının olması gerekmez yapmak beri başlamış edildiği).
Peki ya bu sonuçları bir işlevden döndürmeniz gerekirse ? Diğer cevapların da işaret ettiği gibi, yapamazsınız; işlevinizin bir geri aramayı kabul etmesini ve çağırmasını (veya bir Sözü iade etmesini) sağlamanız gerekir . İşte bir geri arama sürümü:
function doSomethingWith(theArray, callback) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
callback(results);
}
});
});
}
doSomethingWith(theArray, function(results) {
console.log("Results:", results);
});
Misal:
function doSomethingWith(theArray, callback) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
callback(results);
}
});
});
}
doSomethingWith([1, 2, 3], function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
Veya Promise
bunun yerine a döndüren bir sürüm :
function doSomethingWith(theArray) {
return new Promise(function(resolve) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
resolve(results);
}
});
});
});
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
Elbette, doSomethingAsync
bize hatalar reject
yaparsak, bir hata aldığımızda sözümüzü reddederdik.)
Misal:
function doSomethingWith(theArray) {
return new Promise(function(resolve) {
var results = [];
var expecting = theArray.length;
theArray.forEach(function(entry, index) {
doSomethingAsync(entry, function(result) {
results[index] = result;
if (--expecting === 0) {
// Done!
resolve(results);
}
});
});
});
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
(Veya alternatif olarak, bunun için doSomethingAsync
bir söz döndüren bir sarmalayıcı yapabilir ve sonra aşağıdakileri yapabilirsiniz ...)
Eğer doSomethingAsync
size verir Promise , şunları kullanabilirsiniz Promise.all:
function doSomethingWith(theArray) {
return Promise.all(theArray.map(function(entry) {
return doSomethingAsync(entry);
}));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
doSomethingAsync
Bunun ikinci ve üçüncü bir argümanı yok sayacağını biliyorsanız, onu doğrudan şuna iletebilirsiniz map
( map
geri aramasını üç argümanla çağırır, ancak çoğu insan çoğu zaman yalnızca ilkini kullanır):
function doSomethingWith(theArray) {
return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
Misal:
function doSomethingWith(theArray) {
return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
.as-console-wrapper {
max-height: 100% !important;
}
Unutmayın ki Promise.all
, sözünü, hepsi çözüldüğünde verdiğiniz tüm sözlerin sonuçlarının bir dizisi ile çözer veya verdiğiniz sözlerden ilkini reddettiğinde sözünü reddeder.
Dizi
Diyelim ki operasyonların paralel olmasını istemiyorsunuz? Bunları birbiri ardına çalıştırmak istiyorsanız, bir sonrakine başlamadan önce her işlemin tamamlanmasını beklemeniz gerekir. İşte bunu yapan ve sonuçla bir geri aramayı çağıran bir işlev örneği:
function doSomethingWith(theArray, callback) {
var results = [];
doOne(0);
function doOne(index) {
if (index < theArray.length) {
doSomethingAsync(theArray[index], function(result) {
results.push(result);
doOne(index + 1);
});
} else {
// Done!
callback(results);
}
}
}
doSomethingWith(theArray, function(results) {
console.log("Results:", results);
});
(Çalışmayı seri olarak yaptığımız için results.push(result)
, sırayla sonuç almayacağımızı bildiğimiz için kullanabiliriz . Yukarıdakileri kullanabilirdik results[index] = result;
, ancak aşağıdaki örneklerden bazılarında bir indeksimiz yok kullanmak.)
Misal:
function doSomethingWith(theArray, callback) {
var results = [];
doOne(0);
function doOne(index) {
if (index < theArray.length) {
doSomethingAsync(theArray[index], function(result) {
results.push(result);
doOne(index + 1);
});
} else {
// Done!
callback(results);
}
}
}
doSomethingWith([1, 2, 3], function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value, callback) {
console.log("Starting async operation for " + value);
setTimeout(function() {
console.log("Completing async operation for " + value);
callback(value * 2);
}, Math.floor(Math.random() * 200));
}
.as-console-wrapper {
max-height: 100% !important;
}
(Ya da doSomethingAsync
bunun için size bir söz verir ve aşağıdakileri yapın ...)
Eğer doSomethingAsync
size Sözü verir size (belki gibi bir transpiler ile ES2017 + sözdizimini kullanabilirsiniz eğer, Babil ), bir kullanabilir asyncfonksiyonu ile for-ofve await:
async function doSomethingWith(theArray) {
const results = [];
for (const entry of theArray) {
results.push(await doSomethingAsync(entry));
}
return results;
}
doSomethingWith(theArray).then(results => {
console.log("Results:", results);
});
Misal:
async function doSomethingWith(theArray) {
const results = [];
for (const entry of theArray) {
results.push(await doSomethingAsync(entry));
}
return results;
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
.as-console-wrapper {
max-height: 100% !important;
}
ES2017 + sözdizimini (henüz) kullanamıyorsanız, "Söz azaltma" modelinin bir varyasyonunu kullanabilirsiniz (bu, normal Promise indirgemesinden daha karmaşıktır çünkü sonucu birinden diğerine aktarmıyoruz, bunun yerine sonuçlarını bir dizide toplamak):
function doSomethingWith(theArray) {
return theArray.reduce(function(p, entry) {
return p.then(function(results) {
return doSomethingAsync(entry).then(function(result) {
results.push(result);
return results;
});
});
}, Promise.resolve([]));
}
doSomethingWith(theArray).then(function(results) {
console.log("Results:", results);
});
Misal:
function doSomethingWith(theArray) {
return theArray.reduce(function(p, entry) {
return p.then(function(results) {
return doSomethingAsync(entry).then(function(result) {
results.push(result);
return results;
});
});
}, Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
.as-console-wrapper {
max-height: 100% !important;
}
... ES2015 + ok işlevleriyle daha az külfetli :
function doSomethingWith(theArray) {
return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
results.push(result);
return results;
})), Promise.resolve([]));
}
doSomethingWith(theArray).then(results => {
console.log("Results:", results);
});
Misal:
function doSomethingWith(theArray) {
return theArray.reduce((p, entry) => p.then(results => doSomethingAsync(entry).then(result => {
results.push(result);
return results;
})), Promise.resolve([]));
}
doSomethingWith([1, 2, 3]).then(function(results) {
console.log("Results:", results);
});
function doSomethingAsync(value) {
console.log("Starting async operation for " + value);
return new Promise(function(resolve) {
setTimeout(function() {
console.log("Completing async operation for " + value);
resolve(value * 2);
}, Math.floor(Math.random() * 200));
});
}
.as-console-wrapper {
max-height: 100% !important;
}
Şu örneğe bir göz atın:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope,$http) {
var getJoke = function(){
return $http.get('http://api.icndb.com/jokes/random').then(function(res){
return res.data.value;
});
}
getJoke().then(function(res) {
console.log(res.joke);
});
});
Gördüğünüz gibi getJoke
, çözülmüş bir sözün geri dönmesi (geri döndüğünüzde çözülür ). Bu nedenle, $ http.get isteği tamamlanana kadar beklersiniz ve ardından console.log (res.joke) yürütülür (normal bir zaman uyumsuz akış olarak).res.data.value
Bu plnkr:
http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/
ES6 yolu (eşzamansız - bekleyin)
(function(){
async function getJoke(){
let response = await fetch('http://api.icndb.com/jokes/random');
let data = await response.json();
return data.value;
}
getJoke().then((joke) => {
console.log(joke);
});
})();
Bu, birçok yeni JavaScript çerçevesinde kullanılan iki yollu veri bağlama veya depolama konseptinin sizin için harika çalışacağı yerlerden biridir ...
Dolayısıyla , Angular, React veya iki yolla veri bağlama veya depolama konsepti yapan başka bir çerçeve kullanıyorsanız , bu sorun sizin için basitçe düzeltilmiştir, bu nedenle basit bir deyişle undefined
, sonucunuz ilk aşamadadır, bu nedenle result = undefined
, veriler, ardından sonucu alır almaz güncellenecek ve Ajax çağrınızın yanıtının hangi yeni değere atanacağı ...
Ancak , örneğin bu soruda sorduğunuz gibi , saf javascript veya jQuery'de bunu nasıl yapabilirsiniz ?
Bir geri arama , vaat ve son zamanlarda gözlemlenebilir olanı sizin için ele almak için kullanabilirsiniz, örneğin, benzer bir fonksiyona sahip olduğumuz success()
veya then()
verileriniz sizin için hazır olduğunda uygulanacak olan sözlerde , gözlemlenebilir üzerindeki geri arama veya abone olma fonksiyonunda olduğu gibi .
Örneğin , jQuery kullandığınız durumda , şöyle bir şey yapabilirsiniz:
$(document).ready(function(){ function foo() { $.ajax({url: "api/data", success: function(data){
fooDone(data); //after we have data, we pass it to fooDone
}});
};
function fooDone(data) {
console.log(data); //fooDone has the data and console.log it
};
foo(); //call happens here
});
Bu eşzamansız şeyleri yapmanın daha yeni yolları olan vaatler ve gözlemlenebilirler hakkında daha fazla bilgi için çalışma .
JavaScript'in 'gizemleri' ile uğraşırken karşılaştığımız çok yaygın bir sorundur. Bugün bu gizemi çözmeyi deneyeyim.
Basit bir JavaScript işleviyle başlayalım:
function foo(){
// do something
return 'wohoo';
}
let bar = foo(); // bar is 'wohoo' here
Bu basit bir eşzamanlı işlev çağrısıdır (her kod satırının sırayla bir sonrakinden önce 'işiyle bittiği') ve sonuç beklendiği gibi aynıdır.
Şimdi, fonksiyonumuza biraz gecikme ekleyerek biraz bükülme ekleyelim, böylece tüm kod satırları sırayla 'bitmiş' olmaz. Böylece, işlevin eşzamansız davranışını taklit edecektir:
function foo(){
setTimeout( ()=>{
return 'wohoo';
}, 1000 )
}
let bar = foo() // bar is undefined here
İşte buyrun, bu gecikme beklediğimiz işlevselliği bozdu! Ama tam olarak ne oldu? Koda bakarsanız aslında oldukça mantıklı. işlev foo()
, çalıştırıldığında hiçbir şey döndürmez (bu nedenle döndürülen değerdir undefined
), ancak bir zamanlayıcı başlatır ve 1 saniye sonra 'wohoo' döndürmek için bir işlevi çalıştırır. Ama gördüğünüz gibi, bar'a atanan değer, foo () 'dan hemen döndürülen şeylerdir, yani hiçbir şey değildir undefined
.
Peki bu sorunu nasıl çözeceğiz?
Fonksiyonumuza bir SÖZ VERME soralım . Söz gerçekten ne anlama geldiğiyle ilgilidir: bu, işlevin gelecekte alacağı herhangi bir çıktıyı sağlamanızı garanti ettiği anlamına gelir. Öyleyse yukarıdaki küçük sorunumuz için iş başında görelim:
function foo(){
return new Promise( (resolve, reject) => { // I want foo() to PROMISE me something
setTimeout ( function(){
// promise is RESOLVED , when execution reaches this line of code
resolve('wohoo')// After 1 second, RESOLVE the promise with value 'wohoo'
}, 1000 )
})
}
let bar ;
foo().then( res => {
bar = res;
console.log(bar) // will print 'wohoo'
});
Bu nedenle, özet - ajax tabanlı çağrılar vb. Gibi asenkron işlevlerin üstesinden gelmek için resolve
, değere (geri dönmek istediğiniz) bir söz kullanabilirsiniz . Böylece, kısaca , asenkron işlevlerde döndürmek yerine değeri çözümlemiş olursunuz .
GÜNCELLEME (Eşzamansız / bekleme ile vaatler)
Sözlerle then/catch
çalışmak için kullanmanın dışında, bir yaklaşım daha var. Buradaki fikir, eşzamansız bir işlevi tanımak ve ardından bir sonraki kod satırına geçmeden önce sözlerin çözülmesini beklemektir . Hala promises
kaputun altında, ancak farklı bir sözdizimsel yaklaşımla. İşleri daha net hale getirmek için aşağıda bir karşılaştırma bulabilirsiniz:
then / catch version:
function saveUsers(){
getUsers()
.then(users => {
saveSomewhere(users);
})
.catch(err => {
console.error(err);
})
}
eşzamansız / bekleme sürümü:
async function saveUsers(){
try{
let users = await getUsers()
saveSomewhere(users);
}
catch(err){
console.error(err);
}
}
Eşzamansız bir işlevden bir değer döndürmenin diğer bir yaklaşımı, eşzamansız işlevin sonucunu depolayacak bir nesneyi iletmektir.
İşte bunun bir örneği:
var async = require("async");
// This wires up result back to the caller
var result = {};
var asyncTasks = [];
asyncTasks.push(function(_callback){
// some asynchronous operation
$.ajax({
url: '...',
success: function(response) {
result.response = response;
_callback();
}
});
});
async.parallel(asyncTasks, function(){
// result is available after performing asynchronous operation
console.log(result)
console.log('Done');
});
result
Eşzamansız işlem sırasında değeri depolamak için nesneyi kullanıyorum . Bu, sonucun eşzamansız işten sonra bile kullanılabilir olmasını sağlar.
Bu yaklaşımı çok kullanıyorum. Ardışık modüller aracılığıyla sonucu geri kablolamanın dahil olduğu durumlarda bu yaklaşımın ne kadar iyi çalıştığını bilmek isterim.
Sözler ve geri aramalar birçok durumda iyi sonuç verirken, aşağıdaki gibi bir şeyi ifade etmek arkada bir acıdır:
if (!name) {
name = async1();
}
async2(name);
Sonunda geçecektin async1
; name
tanımsız olup olmadığını kontrol edin ve buna göre geri aramayı arayın.
async1(name, callback) {
if (name)
callback(name)
else {
doSomething(callback)
}
}
async1(name, async2)
Öyle olsa tamam küçük örneklerde sen katılan benzer durumlarda ve hata işleme çok şey var zaman can sıkıcı olur.
Fibers
sorunu çözmede yardımcı olur.
var Fiber = require('fibers')
function async1(container) {
var current = Fiber.current
var result
doSomething(function(name) {
result = name
fiber.run()
})
Fiber.yield()
return result
}
Fiber(function() {
var name
if (!name) {
name = async1()
}
async2(name)
// Make any number of async calls from here
}
Projeyi buradan kontrol edebilirsiniz .
Yazdığım aşağıdaki örnek nasıl yapılacağını gösteriyor
- Eşzamansız HTTP çağrılarını işleyin;
- Her API çağrısından yanıt bekleyin;
- Promise kalıbını kullanın ;
- Birden çok HTTP çağrısına katılmak için Promise.all modelini kullanın ;
Bu çalışma örneği bağımsızdır. Çağrı XMLHttpRequest
yapmak için pencere nesnesini kullanan basit bir istek nesnesi tanımlayacaktır . Bir grup sözün tamamlanmasını beklemek için basit bir işlev tanımlayacaktır.
Bağlam. Örnek, belirli bir sorgu dizesi kümesi için nesneleri aramak için Spotify Web API uç noktasını playlist
sorgulamaktır:
[
"search?type=playlist&q=%22doom%20metal%22",
"search?type=playlist&q=Adele"
]
Her öğe için, yeni bir Promise bir blok ExecutionBlock
başlatır - sonucu ayrıştırır, sonuç dizisine, yani Spotify user
nesnelerinin bir listesine dayalı olarak yeni bir vaatler dizisi planlar ve yeni HTTP çağrısını ExecutionProfileBlock
eşzamansız olarak yürütür .
Daha sonra, birden çok ve tamamen eşzamansız iç içe geçmiş HTTP çağrısı oluşturmanıza ve her çağrı alt kümesinden gelen sonuçları birleştirmenize olanak tanıyan iç içe geçmiş bir Promise yapısı görebilirsiniz Promise.all
.
NOT Son Spotify search
API'leri, istek başlıklarında bir erişim belirtecinin belirtilmesini gerektirecektir:
-H "Authorization: Bearer {your access token}"
Bu nedenle, aşağıdaki örneği çalıştırmanız için erişim jetonunuzu istek başlıklarına koymanız gerekir:
var spotifyAccessToken = "YourSpotifyAccessToken";
var console = {
log: function(s) {
document.getElementById("console").innerHTML += s + "<br/>"
}
}
// Simple XMLHttpRequest
// based on https://davidwalsh.name/xmlhttprequest
SimpleRequest = {
call: function(what, response) {
var request;
if (window.XMLHttpRequest) { // Mozilla, Safari, ...
request = new XMLHttpRequest();
} else if (window.ActiveXObject) { // Internet Explorer
try {
request = new ActiveXObject('Msxml2.XMLHTTP');
}
catch (e) {
try {
request = new ActiveXObject('Microsoft.XMLHTTP');
} catch (e) {}
}
}
// State changes
request.onreadystatechange = function() {
if (request.readyState === 4) { // Done
if (request.status === 200) { // Complete
response(request.responseText)
}
else
response();
}
}
request.open('GET', what, true);
request.setRequestHeader("Authorization", "Bearer " + spotifyAccessToken);
request.send(null);
}
}
//PromiseAll
var promiseAll = function(items, block, done, fail) {
var self = this;
var promises = [],
index = 0;
items.forEach(function(item) {
promises.push(function(item, i) {
return new Promise(function(resolve, reject) {
if (block) {
block.apply(this, [item, index, resolve, reject]);
}
});
}(item, ++index))
});
Promise.all(promises).then(function AcceptHandler(results) {
if (done) done(results);
}, function ErrorHandler(error) {
if (fail) fail(error);
});
}; //promiseAll
// LP: deferred execution block
var ExecutionBlock = function(item, index, resolve, reject) {
var url = "https://api.spotify.com/v1/"
url += item;
console.log( url )
SimpleRequest.call(url, function(result) {
if (result) {
var profileUrls = JSON.parse(result).playlists.items.map(function(item, index) {
return item.owner.href;
})
resolve(profileUrls);
}
else {
reject(new Error("call error"));
}
})
}
arr = [
"search?type=playlist&q=%22doom%20metal%22",
"search?type=playlist&q=Adele"
]
promiseAll(arr, function(item, index, resolve, reject) {
console.log("Making request [" + index + "]")
ExecutionBlock(item, index, resolve, reject);
}, function(results) { // Aggregated results
console.log("All profiles received " + results.length);
//console.log(JSON.stringify(results[0], null, 2));
///// promiseall again
var ExecutionProfileBlock = function(item, index, resolve, reject) {
SimpleRequest.call(item, function(result) {
if (result) {
var obj = JSON.parse(result);
resolve({
name: obj.display_name,
followers: obj.followers.total,
url: obj.href
});
} //result
})
} //ExecutionProfileBlock
promiseAll(results[0], function(item, index, resolve, reject) {
//console.log("Making request [" + index + "] " + item)
ExecutionProfileBlock(item, index, resolve, reject);
}, function(results) { // aggregated results
console.log("All response received " + results.length);
console.log(JSON.stringify(results, null, 2));
}
, function(error) { // Error
console.log(error);
})
/////
},
function(error) { // Error
console.log(error);
});
<div id="console" />
Bu çözümü burada kapsamlı bir şekilde tartıştım .
Kısa cevap, şöyle bir geri arama uygulamanız gerekir:
function callback(response) {
// Here you can do what ever you want with the response object.
console.log(response);
}
$.ajax({
url: "...",
success: callback
});
2017 cevabı: artık her mevcut tarayıcıda ve düğümde tam olarak istediğinizi yapabilirsiniz
Bu oldukça basit:
- Söz Ver
- JavaScript'e bir değere (HTTP yanıtı gibi) çözülme vaadini beklemesini söyleyen 'await' seçeneğini kullanın.
- 'Eşzamansız' anahtar kelimeyi üst işleve ekleyin
İşte kodunuzun çalışan bir sürümü:
(async function(){
var response = await superagent.get('...')
console.log(response)
})()
await, tüm mevcut tarayıcılarda ve düğüm 8'de desteklenmektedir
Js, tek iş parçacıklıdır.
Tarayıcı üç bölüme ayrılabilir:
1) Etkinlik Döngüsü
2) Web API
3) Olay Sırası
Olay Döngüsü sonsuza kadar çalışır, yani sonsuz döngüdür.Olay Kuyruğu, tüm işlevinizin bazı olaylara (örneğin: tıklama) itildiği yerdir; bu, sıradan teker teker gerçekleştirilir ve bu işlevi çalıştıran ve kendi kendini hazırlayan Olay döngüsüne yerleştirilir. Bu, bir işlevin çalıştırılmasının, işlev kuyruktaki işlev olay döngüsünde yürütülmeden önce başlamadığı anlamına gelir.
Şimdi bir kuyrukta iki işlevi ittiğimizi düşünelim, biri sunucudan bir veri almak için, diğeri bu verileri kullanmak için. ServerRequest () işlevini önce queue'da, sonra utiliseData () işlevini ittik. serverRequest işlevi olay döngüsüne girer ve sunucudan veri almanın ne kadar zaman alacağını asla bilemediğimiz için sunucuya bir çağrı yapar, bu nedenle bu işlemin zaman alması beklenir ve bu nedenle olay döngümüzü meşgul ederiz, böylece sayfamızı asarız, işte burada Web API, bu işlevi olay döngüsünden alır ve sunucunun olay döngüsünü serbest bırakmasıyla ilgilenir, böylece kuyruktaki bir sonraki işlevi çalıştırabiliriz. Kuyruktaki bir sonraki işlev, döngüye giren, ancak kullanılabilir veri olmadığından, Bir sonraki işlevin boşa harcanması ve çalıştırılması kuyruğun sonuna kadar devam eder. (Buna Async çağrı denir, yani verileri alana kadar başka bir şey yapabiliriz)
ServerRequest () fonksiyonumuzun bir kodda bir return ifadesi olduğunu varsayalım, sunucudan veri aldığımızda, Web API, kuyruğun sonunda onu kuyruğa itecektir. Kuyruğun sonunda itildiğinden, kuyruğumuzda bu verileri kullanmak için herhangi bir işlev kalmadığından verilerini kullanamayız. Bu nedenle Async Call'dan bir şey döndürmek mümkün değildir.
Bu nedenle buna Çözüm, geri arama veya sözdür .
Buradaki cevaplardan birinden bir resim, Geri arama kullanımını doğru bir şekilde açıklıyor ... Fonksiyonumuzu (sunucudan döndürülen verileri kullanan fonksiyon) çağrı sunucusuna fonksiyon veriyoruz.
function doAjax(callbackFunc, method, url) {
var xmlHttpReq = new XMLHttpRequest();
xmlHttpReq.open(method, url);
xmlHttpReq.onreadystatechange = function() {
if (xmlHttpReq.readyState == 4 && xmlHttpReq.status == 200) {
callbackFunc(xmlHttpReq.responseText);
}
}
xmlHttpReq.send(null);
}
Kodumda buna
function loadMyJson(categoryValue){
if(categoryValue==="veg")
doAjax(print,"GET","http://localhost:3004/vegetables");
else if(categoryValue==="fruits")
doAjax(print,"GET","http://localhost:3004/fruits");
else
console.log("Data not found");
}
Javscript.info geri arama
Uzaktan arama yapmak için bu özel kitaplığı (Promise kullanılarak yazılmıştır) kullanabilirsiniz.
function $http(apiConfig) {
return new Promise(function (resolve, reject) {
var client = new XMLHttpRequest();
client.open(apiConfig.method, apiConfig.url);
client.send();
client.onload = function () {
if (this.status >= 200 && this.status < 300) {
// Performs the function "resolve" when this.status is equal to 2xx.
// Your logic here.
resolve(this.response);
}
else {
// Performs the function "reject" when this.status is different than 2xx.
reject(this.statusText);
}
};
client.onerror = function () {
reject(this.statusText);
};
});
}
Basit kullanım örneği:
$http({
method: 'get',
url: 'google.com'
}).then(function(response) {
console.log(response);
}, function(error) {
console.log(error)
});
Diğer bir çözüm, kodun sıralı çalıştırıcı nsynjs aracılığıyla yürütülmesidir .
Altta yatan işlev vaat edilirse
nsynjs, tüm vaatleri sırayla değerlendirecek ve vaat sonucunu data
mülke yerleştirecektir:
function synchronousCode() {
var getURL = function(url) {
return window.fetch(url).data.text().data;
};
var url = 'https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
console.log('received bytes:',getURL(url).length);
};
nsynjs.run(synchronousCode,{},function(){
console.log('synchronousCode done');
});
<script src="https://rawgit.com/amaksr/nsynjs/master/nsynjs.js"></script>
Altta yatan işlev vaat edilmemişse
Adım 1. Geri aramayla birlikte işlevini nsynjs-duyarlı sarmalayıcıya sarın (vaat edilmiş bir sürüme sahipse, bu adımı atlayabilirsiniz):
var ajaxGet = function (ctx,url) {
var res = {};
var ex;
$.ajax(url)
.done(function (data) {
res.data = data;
})
.fail(function(e) {
ex = e;
})
.always(function() {
ctx.resume(ex);
});
return res;
};
ajaxGet.nsynjsHasCallback = true;
Adım 2. Senkron mantığı devreye alın:
function process() {
console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);
}
Adım 3. İşlevi nsynjs aracılığıyla eşzamanlı olarak çalıştırın:
nsynjs.run(process,this,function () {
console.log("synchronous function finished");
});
Nsynjs, tüm işleçleri ve ifadeleri adım adım değerlendirecek ve yavaş bir işlevin sonucunun hazır olmaması durumunda yürütmeyi duraklatacaktır.
Burada daha fazla örnek: https://github.com/amaksr/nsynjs/tree/master/examples
ECMAScript 6, eşzamansız bir tarzda kolayca programlama yapmanızı sağlayan 'üreteçlere' sahiptir.
function* myGenerator() {
const callback = yield;
let [response] = yield $.ajax("https://stackoverflow.com", {complete: callback});
console.log("response is:", response);
// examples of other things you can do
yield setTimeout(callback, 1000);
console.log("it delayed for 1000ms");
while (response.statusText === "error") {
[response] = yield* anotherGenerator();
}
}
Yukarıdaki kodu çalıştırmak için şunu yaparsınız:
const gen = myGenerator(); // Create generator
gen.next(); // Start it
gen.next((...args) => gen.next([...args])); // Set its callback function
ES6'yı desteklemeyen tarayıcıları hedeflemeniz gerekiyorsa, ECMAScript 5'i oluşturmak için kodu Babel veya kapatma derleyicisi aracılığıyla çalıştırabilirsiniz.
Geri çağırma ...args
bir diziye sarılır ve bunları okuduğunuzda yok edilir, böylece desen birden çok bağımsız değişkeni olan geri aramalarla baş edebilir. Örneğin fs düğümü ile :
const [err, data] = yield fs.readFile(filePath, "utf-8", callback);
Eşzamansız isteklerle çalışmak için bazı yaklaşımlar şunlardır:
- Tarayıcı Promise nesnesi
- S - JavaScript için bir söz kitaplığı
- A + Promises.js
- jQuery ertelendi
- XMLHttpRequest API
- Geri arama kavramını kullanma - İlk yanıtta uygulama olarak
Örnek: jQuery, uygulamayı birden çok istekle çalışması için erteledi
var App = App || {};
App = {
getDataFromServer: function(){
var self = this,
deferred = $.Deferred(), requests = []; requests.push($.getJSON('request/ajax/url/1'));
requests.push($.getJSON('request/ajax/url/2')); $.when.apply(jQuery, requests).done(function(xhrResponse) {
return deferred.resolve(xhrResponse.result);
});
return deferred;
},
init: function(){
this.getDataFromServer().done(_.bind(function(resp1, resp2) {
// Do the operations which you wanted to do when you
// get a response from Ajax, for example, log response.
}, this));
}
};
App.init();
Kendimizi "zaman" dediğimiz boyutta ilerliyor gibi görünen bir evrende buluyoruz. Zamanın ne olduğunu gerçekten anlamıyoruz, ancak bunun hakkında akıl yürütmemize ve konuşmamıza izin veren soyutlamalar ve sözcükler geliştirdik: "geçmiş", "şimdi", "gelecek", "önce", "sonra".
İnşa ettiğimiz bilgisayar sistemleri - gittikçe daha fazla - önemli bir boyut olarak zamana sahip. Gelecekte bazı şeyler olacak şekilde ayarlanmıştır. Sonunda bu ilk şeyler gerçekleştikten sonra başka şeylerin de olması gerekir. Bu, "eşzamansızlık" denen temel kavramdır. Gittikçe ağlaşan dünyamızda, en yaygın eşzamansızlık durumu, bazı uzak sistemlerin bazı taleplere yanıt vermesini beklemektir.
Bir örnek düşünün. Sütçüyü ara ve biraz süt sipariş et. Geldiğinde kahvenize koymak istersiniz. Sütü şu anda kahvenize koyamazsınız çünkü henüz burada değil. Kahvenize koymadan önce gelmesini beklemelisiniz. Başka bir deyişle, aşağıdakiler işe yaramayacaktır:
var milk = order_milk();
put_in_coffee(milk);
JS o gerektiğini bilmenin bir yolu olmadığı için beklemek için order_milk
çalışmasından önce sona put_in_coffee
. Başka bir deyişle, bunun asenkron olduğunu bilmez - bu order_milk
, gelecekteki bir zamana kadar sütle sonuçlanmayacak bir şeydir. JS ve diğer bildirime dayalı diller, bir ifadeyi beklemeden birbiri ardına yürütür.
Bu soruna klasik JS yaklaşımı, JS'nin, iletilebilen birinci sınıf nesneler olarak işlevleri desteklemesi gerçeğinden yararlanarak, bir işlevi eşzamansız isteğe bir parametre olarak geçirmektir; gelecekte bir ara görevi. Bu "geri arama" yaklaşımıdır. Şuna benziyor:
order_milk(put_in_coffee);
order_milk
başlar, sütü emreder, sonra, ne zaman ve ancak geldiğinde, çağırır put_in_coffee
.
Bu geri arama yaklaşımındaki sorun, sonucunu bildiren bir işlevin normal anlamını kirletmesidir return
; bunun yerine işlevler, parametre olarak verilen bir geri aramayı çağırarak sonuçlarını bildirmemelidir. Ayrıca, bu yaklaşım, daha uzun olay dizileriyle uğraşırken hızla hantal hale gelebilir. Örneğin sütün kahveye konmasını bekledikten sonra ancak o zaman üçüncü bir adım, yani kahveyi içmek istiyorum diyelim. Sonunda şöyle bir şey yazmaya ihtiyacım var:
order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }
put_in_coffee
Hem içine koymak için sütü , hem de süt konulduktan sonra drink_coffee
yürütmek için eylemi ( ) geçiyorum . Bu kodun yazılması, okunması ve hata ayıklaması zorlaşıyor.
Bu durumda, sorudaki kodu şu şekilde yeniden yazabiliriz:
var answer;
$.ajax('/foo.json') . done(function(response) {
callback(response.data);
});
function callback(data) {
console.log(data);
}
Sözleri girin
Bu, bir geleceği veya bir tür eşzamansız sonucu temsil eden belirli bir değer türü olan "vaat" kavramının motivasyonuydu . Zaten olmuş veya gelecekte olacak veya hiç olmayacak bir şeyi temsil edebilir. Vaatlerin then
, vaatin temsil ettiği sonuç gerçekleştiğinde yürütülecek bir eylemi ilettiğiniz, adlandırılmış tek bir yöntemi vardır.
Süt ve kahvemiz order_milk
söz konusu olduğunda, gelen süt için bir söz vermeyi tasarlıyoruz ve ardından aşağıdaki put_in_coffee
gibi bir then
eylem olarak belirtiyoruz:
order_milk() . then(put_in_coffee)
Bunun bir avantajı, gelecekteki oluşumların sıralarını oluşturmak için bunları bir araya getirebilmemizdir ("zincirleme"):
order_milk() . then(put_in_coffee) . then(drink_coffee)
Özel probleminize sözler uygulayalım. İstek mantığımızı, bir vaat döndüren bir işlevin içine yerleştireceğiz:
function get_data() {
return $.ajax('/foo.json');
}
Aslında, yaptığımız tek şey return
aramaya bir eklemek oldu $.ajax
. Bu işe yarıyor çünkü jQuery $.ajax
zaten bir tür vaat benzeri bir şey döndürüyor. (Pratikte, ayrıntılara girmeden, gerçek bir söz vermek için bu çağrıyı tamamlamayı veya buna alternatif bir alternatif kullanmayı tercih $.ajax
ederiz.) Şimdi, dosyayı yüklemek ve bitmesini beklemek istiyorsak ve o zaman bir şeyler yap, basitçe söyleyebiliriz
get_data() . then(do_something)
Örneğin,
get_data() .
then(function(data) { console.log(data); });
then
Sözleri kullandığımızda, birçok işlevi içeri aktarırız, bu nedenle daha kompakt ES6 tarzı ok işlevlerini kullanmak genellikle yararlı olur:
get_data() .
then(data => console.log(data));
async
anahtar kelime
Ancak, eşzamanlıysa tek yönlü ve eşzamansızsa oldukça farklı bir şekilde kod yazmak zorunda kalmanın hala belirsiz bir şekilde tatmin edici olmayan bir yanı var. Senkronize için yazıyoruz
a();
b();
ama a
eşzamansızsa, sözler yazmalıyız
a() . then(b);
Yukarıda, "JS'nin, ikinciyi çalıştırmadan önce ilk çağrının bitmesini beklemesi gerektiğini bilmesinin bir yolu yoktur" demiştik . Orada eğer hoş olmaz idi o JS anlatmak için bir yol? await
"Eşzamansız" işlev adı verilen özel bir işlev türü içinde kullanılan anahtar kelime olduğu ortaya çıktı . Bu özellik ES'nin gelecek sürümünün bir parçasıdır, ancak doğru ön ayarlarla birlikte Babel gibi aktarıcılarda zaten mevcuttur. Bu, basitçe yazmamızı sağlar
async function morning_routine() {
var milk = await order_milk();
var coffee = await put_in_coffee(milk);
await drink(coffee);
}
Senin durumunda, şöyle bir şey yazabilirsin
async function foo() {
data = await get_data();
console.log(data);
}
Kısa cevap : foo()
Yönteminiz hemen dönerken , işlev döndükten sonra$ajax()
çağrı eşzamansız olarak yürütülür . Sorun, döndüğünde zaman uyumsuz çağrı tarafından alınan sonuçların nasıl ve nerede saklanacağıdır.
Bu ileti dizisinde çeşitli çözümler verilmiştir. Belki de en kolay yol, bir nesneyi foo()
yönteme iletmek ve zaman uyumsuz çağrı tamamlandıktan sonra sonuçları o nesnenin bir üyesinde saklamaktır.
function foo(result) {
$.ajax({
url: '...',
success: function(response) {
result.response = response; // Store the async result
}
});
}
var result = { response: null }; // Object to hold the async result
foo(result); // Returns before the async completes
Çağrının foo()
yine de yararlı bir şey döndürmeyeceğini unutmayın . Ancak, zaman uyumsuz çağrının sonucu şimdi içinde saklanacaktır result.response
.
Başarının callback()
içinde bir işlev kullanın foo()
. Bu şekilde deneyin. Anlaşılması basit ve kolaydır.
var lat = "";
var lon = "";
function callback(data) {
lat = data.lat;
lon = data.lon;
}
function getLoc() {
var url = "http://ip-api.com/json"
$.getJSON(url, function(data) {
callback(data);
});
}
getLoc();
Promise Kullanmak
Bu sorunun en mükemmel cevabı kullanmaktır Promise
.
function ajax(method, url, params) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.onload = function() {
resolve(this.responseText);
};
xhr.onerror = reject;
xhr.open(method, url);
xhr.send(params);
});
}
Kullanım
ajax("GET", "/test", "acrive=1").then(function(result) {
// Code depending on result
})
.catch(function() {
// An error occurred
});
Fakat bekle...!
Sözleri kullanmakla ilgili bir sorun var!
Neden kendi özel Sözümüzü kullanmalıyız?
Eski tarayıcılarda bir hata olduğunu anlayana kadar bu çözümü bir süredir kullanıyordum:
Uncaught ReferenceError: Promise is not defined
Bu yüzden ES3 için kendi Promise sınıfımı, tanımlanmamışsa aşağıdaki js derleyicilerine uygulamaya karar verdim . Bu kodu ana kodunuzun önüne ekleyin ve ardından güvenle Promise'ı kullanın!
if(typeof Promise === "undefined"){
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Promise = function () {
function Promise(main) {
var _this = this;
_classCallCheck(this, Promise);
this.value = undefined;
this.callbacks = [];
var resolve = function resolve(resolveValue) {
_this.value = resolveValue;
_this.triggerCallbacks();
};
var reject = function reject(rejectValue) {
_this.value = rejectValue;
_this.triggerCallbacks();
};
main(resolve, reject);
}
Promise.prototype.then = function then(cb) {
var _this2 = this;
var next = new Promise(function (resolve) {
_this2.callbacks.push(function (x) {
return resolve(cb(x));
});
});
return next;
};
Promise.prototype.catch = function catch_(cb) {
var _this2 = this;
var next = new Promise(function (reject) {
_this2.callbacks.push(function (x) {
return reject(cb(x));
});
});
return next;
};
Promise.prototype.triggerCallbacks = function triggerCallbacks() {
var _this3 = this;
this.callbacks.forEach(function (cb) {
cb(_this3.value);
});
};
return Promise;
}();
}
Soru şuydu:
Eşzamansız bir aramadan gelen yanıtı nasıl döndürürüm?
hangisi şu şekilde yorumlanabilir:
Nasıl yapmak için asenkron kod bakmak senkron ?
Çözüm, geri aramalardan kaçınmak ve Promises ve async / await kombinasyonunu kullanmak olacaktır .
Bir Ajax talebi için bir örnek vermek istiyorum.
(Javascript ile yazılabilse de Python'da yazmayı ve Transcrypt kullanarak Javascript'e derlemeyi tercih ediyorum . Yeterince açık olacak.)
Öncelikle JQuery kullanımını etkinleştirip şu şekilde $
kullanılabilir hale gelelim S
:
__pragma__ ('alias', 'S', '$')
Promise döndüren bir işlev tanımlayın , bu durumda bir Ajax çağrısı:
def read(url: str):
deferred = S.Deferred()
S.ajax({'type': "POST", 'url': url, 'data': { },
'success': lambda d: deferred.resolve(d),
'error': lambda e: deferred.reject(e)
})
return deferred.promise()
Kullanım asenkron o sanki kodu senkron :
async def readALot():
try:
result1 = await read("url_1")
result2 = await read("url_2")
except Exception:
console.warn("Reading a lot failed")
Elbette eşzamanlı istek, söz gibi birçok yaklaşım var, ancak deneyimlerime göre geri arama yaklaşımını kullanmanız gerektiğini düşünüyorum. Javascript'in eşzamansız davranışı doğaldır. Dolayısıyla, kod pasajınız biraz farklı şekilde yeniden yazılabilir:
function foo() {
var result;
$.ajax({
url: '...',
success: function(response) {
myCallback(response);
}
});
return result;
}
function myCallback(response) {
// Does something.
}
Buradaki tüm yanıtları ve deneyimlerimi okuduktan sonra callback, promise and async/await
, JavaScript'teki asenkron programlamanın detayına devam etmek istiyorum .
1) Geri Arama: Geri aramanın temel nedeni, bir olaya yanıt olarak kod çalıştırmaktır (aşağıdaki örneğe bakın). Her seferinde JavaScript'te geri arama kullanıyoruz.
const body = document.getElementsByTagName('body')[0];
function callback() {
console.log('Hello');
}
body.addEventListener('click', callback);
Ancak, aşağıdaki örnekte çok sayıda iç içe geçmiş geri çağırma kullanmanız gerekiyorsa, kod yeniden düzenleme için korkunç olacaktır.
asyncCallOne(function callback1() {
asyncCallTwo(function callback2() {
asyncCallThree(function callback3() {
...
})
})
})
2) Promise: Bir sözdizimi ES6 - Promise geri arama cehennemi sorununu çözer!
const myFirstPromise = new Promise((resolve, reject) => {
// We call resolve(...) when what we were doing asynchronously was successful, and reject(...) when it failed.
// In this example, we use setTimeout(...) to simulate async code.
// In reality, you will probably be using something like XHR request or an HTML5 API.
setTimeout(() => {
resolve("Success!") // Yay! Everything went well!
}, 250)
})
myFirstPromise
.then((res) => {
return res.json();
})
.then((data) => {
console.log(data);
})
.catch((e) => {
console.log(e);
});
myFirstPromise, eşzamansız kod sürecini temsil eden bir Promise örneğidir. Çözümleme işlevi, Promise örneğinin bittiğini gösterir. Daha sonra, vaat örneğinde .then () (istediğiniz gibi. Sonra zinciri) ve .catch () çağırabiliriz:
then — Runs a callback you pass to it when the promise has fulfilled.
catch — Runs a callback you pass to it when something went wrong.
3) Async / Await : yeni bir sözdizimi ES6 - Await, temelde Promise'in şeker sözdizimidir!
Zaman uyumsuz işlev, vaatlerle elde edeceğimiz sonucu elde etmek için daha az kod yazmamızı sağlayan temiz ve kısa bir sözdizimi sağlar. Async / Await, eşzamanlı koda benzer ve eşzamanlı kodun okunması ve yazılması çok daha kolaydır. Async / Await ile hataları yakalamak için bloğu kullanabiliriz try...catch
. Burada, Promise sözdiziminden bir .sonra () zinciri yazmanıza gerek yoktur.
const getExchangeRate = async () => {
try {
const res = await fetch('https://getExchangeRateData');
const data = await res.json();
console.log(data);
} catch (err) {
console.error(err);
}
}
getExchangeRate();
Sonuç: Bunlar, JavaScript'te eşzamansız programlama için iyi anlamanız gereken tamamen üç sözdizimidir. Bu nedenle, mümkünse, eşzamansız kodlarınızı yeniden düzenlemek için "promise" veya "async / await" kullanmanızı tavsiye ederim (çoğunlukla XHR istekleri için) !
Size kod atmak yerine, JS'nin geri aramaları ve zaman uyumsuzluğu nasıl işlediğini anlamanın anahtarı olan 2 kavram vardır. (bu bir kelime mi?)
Etkinlik Döngüsü ve Eş Zamanlılık Modeli
Bilmeniz gereken üç şey var; Kuyruk; olay döngüsü ve yığın
Geniş, basit terimlerle, olay döngüsü proje yöneticisi gibidir, çalıştırmak isteyen ve kuyruk ile yığın arasında iletişim kuran herhangi bir işlevi sürekli olarak dinler.
while (queue.waitForMessage()) {
queue.processNextMessage();
}
Bir şeyi çalıştırmak için bir mesaj aldığında, onu kuyruğa ekler. Sıra, yürütülmeyi bekleyen şeylerin listesidir (AJAX isteğiniz gibi). bunu şöyle hayal edin:
1. call foo.com/api/bar using foobarFunc
2. Go perform an infinite loop
... and so on
Bu mesajlardan biri yürütüldüğünde, mesajı kuyruktan çıkarır ve bir yığın oluşturur, yığın, JS'nin mesajdaki talimatı gerçekleştirmek için yürütmesi gereken her şeydir. Yani bizim örneğimizde aramak için söylendifoobarFunc
function foobarFunc (var) {
console.log(anotherFunction(var));
}
Yani foobarFunc'ın yürütmesi gereken her şey (bizim durumumuzda anotherFunction
) yığına itilecektir. çalıştırılır ve sonra unutulur - olay döngüsü daha sonra kuyruktaki bir sonraki şeye geçer (veya mesajları dinler)
Buradaki kilit nokta, yürütme emridir. Yani
NE ZAMAN bir şey koşacak
AJAX kullanarak harici bir tarafa çağrı yaptığınızda veya herhangi bir asenkron kodu çalıştırdığınızda (örneğin bir setTimeout), Javascript devam etmeden önce bir yanıta bağımlıdır.
Asıl soru, cevabı ne zaman alacak? Cevap bilmiyoruz - bu yüzden olay döngüsü bu mesajın "hey beni çalıştır" demesini bekliyor. JS, bu mesajı eşzamanlı olarak beklediyse, uygulamanız donacak ve emecektir. Bu nedenle JS, mesajın sıraya geri eklenmesini beklerken sıradaki bir sonraki öğeyi yürütmeye devam eder.
Bu nedenle, eşzamansız işlevsellikle geri arama adı verilen şeyler kullanırız . Kelimenin tam anlamıyla bir söz gibi . Bir noktada bir şeyi geri vereceğime söz veriyorum jQuery deffered.done
deffered.fail
ve deffered.always
(diğerleri arasında) adı verilen belirli geri aramaları kullanır . Hepsini burada görebilirsin
Yani yapmanız gereken şey, kendisine aktarılan verilerle bir noktada çalıştırılması vaat edilen bir işlevi iletmektir.
Bir geri arama hemen yürütülmediğinden, ancak daha sonra başvuruyu çalıştırmadığı işleve iletmek önemlidir. yani
function foo(bla) {
console.log(bla)
}
böylece çoğu zaman (ama her zaman değil) size geçmek olacak foo
değilfoo()
Umarım bu biraz mantıklı olacaktır. Bunun gibi kafa karıştırıcı şeylerle karşılaştığınızda - en azından bir anlayışa sahip olmak için belgeleri tamamen okumanızı şiddetle tavsiye ederim. Sizi çok daha iyi bir geliştirici yapacak.