→ Farklı örneklerle zaman uyumsuz davranışın daha genel bir açıklaması için, bkz. Değişkenim bir işlev içinde değiştirdikten sonra neden değişmedi? - Eşzamansız kod referansı
→ Sorunu zaten anlıyorsanız, aşağıdaki olası çözümlere atlayın.
Sorun
Bir de Ajax açılımı asenkron . Bu, isteğin gönderilmesinin (veya yanıtın alınmasının) normal yürütme akışından alındığı anlamına gelir. Örneğinizde, $.ajax
hemen döner ve bir sonraki ifade, geri arama olarak return result;
ilettiğiniz işlev success
çağrılmadan önce yürütülür .
İşte umarım senkron ve asenkron akış arasındaki farkı netleştiren bir benzetme:
Senkron
Bir arkadaşınıza telefon görüşmesi yaptığınızı ve sizin için bir şeyler aramasını istediğinizi düşünün. Biraz zaman alsa da, arkadaşınız size gereken cevabı verene kadar telefonda bekler ve uzaya bakarsınız.
"Normal" kod içeren bir işlev çağrısı yaptığınızda da aynı şey gerçekleşir:
function findItem() {
var item;
while(item_not_found) {
// search
}
return item;
}
var item = findItem();
// Do something with item
doSomethingElse();
Çalıştırılması findItem
uzun zaman alsa da, herhangi bir kodvar item = findItem();
işlevin sonucu döndürmesini beklemek zorundadır .
eşzamanlı olmayan
Aynı nedenden dolayı arkadaşını tekrar ararsın. Ama bu sefer ona aceleniz olduğunu söylüyorsunuz ve sizi geri aramalı sizi cep telefonunuzdan . Telefonu kapat, evi terk et ve ne yapmayı planlıyorsan yap. Arkadaşınız sizi aradığında, size verdiği bilgilerle ilgileniyorsunuz.
Bir Ajax isteği yaptığınızda olan şey tam olarak budur.
findItem(function(item) {
// Do something with item
});
doSomethingElse();
Yanıt beklemek yerine, yürütme hemen devam eder ve Ajax çağrısı yürütüldükten sonra ifade verilir. Sonunda yanıtı almak için, yanıt alındıktan sonra çağrılacak bir işlev, bir geri arama (bir şey dikkat? Geri arama ?) Sağlarsınız . Bu çağrıdan sonra gelen tüm ifadeler, 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ı eşdeğerler sağlasa da ("Ajax" da), özellikle tarayıcı bağlamında genellikle kullanılması önerilmez.
Neden soruyorsun?
JavaScript, tarayıcının UI iş parçacığında çalışır ve uzun süren herhangi bir işlem, kullanıcı arayüzünü kilitleyerek yanıt vermez. Ayrıca, 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 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ş bağlantısı olan kullanıcılar için bu etki daha kötü olacaktır.
Aşağıda, hepsi birbirinin üzerine inşa edilmiş üç farklı çözüme bakacağız:
async/await
Vaat ediyor (ES2017 +, bir transpiler veya rejeneratör kullanıyorsanız eski tarayıcılarda kullanılabilir)
- Geri aramalar (düğümde popüler)
- Sözler
then()
(ES2015 +, çok sayıda söz kitaplığından birini kullanırsanız eski tarayıcılarda kullanılabilir)
Her üçü de mevcut tarayıcılarda ve 7+ düğümünde kullanılabilir.
ES2017 +: ile vaat ediyor async/await
2017'de yayınlanan ECMAScript sürümü , eşzamansız işlevler için sözdizimi düzeyinde destek tanıttı . Yardımıyla async
ve await
bir "senkron tarzı" asenkron yazabilir. Kod hala eşzamansız, ancak okunması / anlaşılması daha kolay.
async/await
vaatlerin üzerine kurulur: bir async
işlev her zaman bir vaat döndürür. await
bir sözün "paketini açar" ve sözün çözümlendiği değere neden olur veya sözün reddedilmesi durumunda bir hata atar.
Önemli: Yalnızca await
bir async
işlevin içinde kullanabilirsiniz . Şu anda, üst düzey await
henüz desteklenmediğinden, bir bağlam başlatmak için zaman uyumsuz IIFE ( Hemen Çağırılan İşlev İfadesi ) yapmanız gerekebilir async
.
MDN hakkında async
ve await
MDN hakkında daha fazla bilgi edinebilirsiniz .
Yukarıdaki gecikme üzerine inşa edilmiş 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);
})();
Geçerli tarayıcı ve düğüm sürümleri desteği async/await
. Kodunuzu, rejeneratör (veya Babel gibi rejeneratör kullanan araçlar) yardımıyla ES5'e dönüştürerek eski ortamları da destekleyebilirsiniz .
İşlevlerin geri aramaları kabul etmesine izin ver
Geri arama, başka bir işleve iletilen bir işlevdir. Bu diğer işlev hazır olduğunda iletilen işlevi çağırabilir. Bir eşzamansız işlem bağlamında, eşzamansız işlem yapıldığında geri arama çağrılacaktır. Genellikle sonuç geri aramaya aktarılır.
Soru örneğinde, foo
geri aramayı kabul edebilir ve 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 "inline" fonksiyonunu tanımladık, ancak herhangi bir fonksiyon referansını 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 fonksiyona atıfta bulunacağız ve sadece ona geçiyoruz success
. Yani Ajax isteği başarılı olduğunda, $.ajax
geri çağrıyı callback
yanıtlar ve geri aramaya iletir (result
tanımlandığımız için ).
Ayrıca geri aramaya geçmeden önce yanıtı 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 çok olay odaklı (DOM olayları). Ajax yanıtı almak bir olaydan başka bir şey değildir.
Üçüncü taraf koduyla çalışmak zorunda olduğunuzda zorluklar ortaya çıkabilir, ancak çoğu sorun sadece uygulama akışını düşünerek çözülebilir.
ES2015 +: ile vaat ediyor ()
Promise API ECMAScript'e 6 (ES2015) yeni bir özelliktir, ama iyi vardır tarayıcı desteği zaten. Standart Promises API'sını uygulayan ve asenkron işlevlerin (örneğin mavi kuş ) kullanımını ve bileşimini kolaylaştırmak için ek yöntemler sağlayan birçok kütüphane de vardır .
Vaatler, gelecekteki değerler için kaplardır . Söz değeri aldığında ( çözüldüğünde ) veya iptal edildiğinde ( reddedildiğinde ), bu değere erişmek isteyen tüm "dinleyicilerini" bildirir.
Düz geri aramalara göre avantajı, kodunuzu ayırmanıza izin vermesi ve oluşturması daha kolay olmasıdır.
İşte söz vermenin 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ıza uygulandığında şu 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
});
Vaat edilen tüm avantajları tanımlamak bu cevabın kapsamı dışındadır, ancak yeni 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 rocks - JavaScript Vaatleri
Yan not: jQuery'nin ertelenmiş nesneleri
Ertelenen nesneler , jQuery'nin özel vaat uygulamalarıdır (Promise API'sı standartlaştırılmadan önce). Neredeyse vaatler gibi davranıyorlar, ancak biraz farklı bir API ortaya koyuyorlar.
JQuery'nin her Ajax yöntemi zaten işlevinizden dönebileceğiniz bir "ertelenmiş nesne" (aslında ertelenmiş bir nesne sözü) 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 sadece 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ş nesnesini döndürür. Bu, if
ifadenin her zaman bu Ertelenmiş nesneyi alacağı, ona davranacağı true
ve kullanıcı oturum açmış gibi ilerleyeceği anlamına gelir . İyi değil.
Ancak düzeltme 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
});
Tavsiye edilmiyor: Senkron "Ajax" çağrıları
Bahsettiğim gibi, bazı (!) Eşzamansız işlemlerin eşzamanlı eşleri vardır. Kullanımlarını savunmuyorum, ancak tamlık uğruna, senkronize bir çağrı nasıl yapılır:
JQuery olmadan
Doğrudan bir XMLHTTPRequest
nesne 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 yine de bir success
geri arama kullanabilir 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
).
Dikkat et! 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).