Eşzamansız bir çağrıdan yanıtı nasıl geri gönderebilirim?


5511

fooAjax isteği yapan bir işlevi var . Yanıtı nasıl geri gönderebilirim foo?

successGeri aramadan değer döndürmeyi denedim , yanı sıra işlev içindeki yerel bir değişkene yanıt atama ve bunu döndürmeyi denedim , ancak bu yolların hiçbiri aslında yanıtı döndürmedi.

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`.

Yanıtlar:


5703

→ 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, $.ajaxhemen 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ı findItemuzun 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/awaitVaat ediyor (ES2017 +, bir transpiler veya rejeneratör kullanıyorsanız eski tarayıcılarda kullanılabilir)
  • Geri aramalar (düğümde popüler)
  • Sözlerthen() (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 asyncve awaitbir "senkron tarzı" asenkron yazabilir. Kod hala eşzamansız, ancak okunması / anlaşılması daha kolay.

async/awaitvaatlerin üzerine kurulur: bir asyncişlev her zaman bir vaat döndürür. awaitbir 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 awaitbir asyncişlevin içinde kullanabilirsiniz . Şu anda, üst düzey awaithenü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 asyncve awaitMDN 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, foogeri aramayı kabul edebilir ve successgeri 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
    });
}

callbackfooonu ç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, $.ajaxgeri çağrıyı callbackyanı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, ififadenin her zaman bu Ertelenmiş nesneyi alacağı, ona davranacağı trueve 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 XMLHTTPRequestnesne kullanıyorsanız, falseüçüncü argüman olarak iletin .open.

jQuery

JQuery kullanıyorsanız , asyncseç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 successgeri 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, $.getJSONvb, 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).


74
@Pommy: jQuery kullanmak istiyorsanız, eklemeniz gerekir. Lütfen docs.jquery.com/Tutorials:Getting_Started_with_jQuery adresine bakın .
Felix Kling

11
Çözüm 1, alt jQuery'de, bu satırı anlayamadım: If you use any other jQuery AJAX method, such as $.get, $.getJSON, etc., you have them to $.ajax.(Evet,
nickimin

32
@gibberish: Mmmh, nasıl daha net hale getirilebileceğini bilmiyorum. Nasıl fooçağrıldığını ve ona bir işlev iletildiğini görüyor musunuz ( foo(function(result) {....});)? resultbu işlevin içinde kullanılır ve Ajax isteğinin yanıtıdır. Bu işleve başvurmak için, anonim işlev yerine foo'nun ilk parametresi çağrılır callbackve atanır success. Yani, isteğin başarılı olduğu zamanı $.ajaxarayacaktır callback. Biraz daha açıklamaya çalıştım.
Felix Kling

43
Bu soru için Sohbet öldü, bu yüzden özetlenen değişiklikleri nerede önereceğimden emin değilim, ama öneriyorum: 1) Eşzamanlı parçayı, nasıl yapılacağına dair hiçbir kod örneği olmadan neden kötü olduğuna dair basit bir tartışma için değiştirin. 2) Sadece daha esnek Ertelenmiş yaklaşımı göstermek için geri arama örneklerini kaldırın / birleştirin.
Chris Moschini

14
@ Jessi: Sanırım cevabın bu kısmını yanlış anladınız. $.getJSONAjax isteğinin senkronize olmasını istiyorsanız kullanamazsınız . Ancak, etkinliğin isteğin eşzamanlı olmasını istememelisiniz, bu geçerli değildir. Yanıtın daha önce açıklandığı gibi, yanıtın üstesinden gelmek için geri aramalar veya vaatler kullanmalısınız.
Felix Kling

1071

Eğer değilseniz 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 insanlar için bir cevap yazma iyi bir iş yaptı, ben olmayan insanlar için bir alternatif sağlamaya karar verdim.

( Yeni fetchAPI, Angular veya sözleri kullananlar için aşağıya başka bir cevap ekledim )


Karşılaştığınız şey

Bu, diğer cevabın "Sorunun açıklaması" nın kısa bir özetidir, eğer bunu okuduktan sonra emin değilseniz, bunu okuyun.

Bir 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, .sendhemen 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 .

Bu, geri döndüğünüzde, tanımladığınız dinleyici henüz yürütülmediği anlamına gelir; bu da, 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)

aDöndürülen değer undefined, a=5parça henüz yürütülmediğinden beri . AJAX böyle davranır, sunucu tarayıcınıza bu değerin ne olduğunu söyleme şansı elde etmeden önce değeri döndürürsünüz.

Bu soruna olası bir çözüm , hesaplama tamamlandığında programınıza ne yapılacağını söyleyerek yeniden aktif olarak kodlamaktı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, geçiyoruzgetFive tamamlandığında gerçekleştirilecek bir eylemden , kodumuza bir etkinlik tamamlandığında nasıl tepki verileceğini söylüyoruz (AJAX çağrımız veya bu durumda zaman aşımı gibi).

Kullanımı:

getFive(onComplete);

Hangi ekrana "5" uyarmalıdır. (Vaktini boşa harcamak) .

Olası çözümler

Bunu çözmenin temel olarak iki yolu vardır:

  1. AJAX çağrısını senkronize hale getirin (buna SJAX diyelim).
  2. Kodunuzu geri aramalarla düzgün çalışacak şekilde yeniden yapılandırın.

1. Senkron AJAX - Yapma !!

Senkron AJAX'a gelince, yapma! Felix'in cevabı, bunun neden kötü bir fikir olduğu konusunda bazı zorlayıcı argümanlar ortaya çıkarıyor. Özetlemek gerekirse, sunucu yanıtı döndürene ve kullanıcının çok kötü bir kullanıcı deneyimi yaratana kadar kullanıcının tarayıcısını dondurur. İşte MDN'den neden hakkında kısa bir özet daha:

XMLHttpRequest hem eşzamanlı hem de eşzamansız iletişimi destekler. Bununla birlikte, genel olarak, performans nedenleriyle senkronize taleplere asenkron talepler tercih edilmelidir.

Kısacası, eşzamanlı 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. Yeniden yapılandırma kodu

İşlevin geri aramayı kabul etmesine izin ver. Örnek kodda foobir geri çağrıyı kabul etmek için yapılabilir. Kodumuza , footamamlandığında nasıl tepki verileceğini söyleyeceğiz .

Yani:

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

Oluyor:

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

Burada anonim bir işlev geçtik, ancak var olan bir işleve kolayca bir referans gönderebildik ve aşağıdaki gibi görünebildik:

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

Bu tür geri arama tasarımlarının nasıl yapıldığına ilişkin daha fazla ayrıntı için Felix'in cevabını kontrol edin.

Şimdi foo'nun kendisini buna göre hareket etmesi için 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)

Artık foo fonksiyonumuzu AJAX başarıyla tamamlandığında gerçekleştirilecek bir eylemi kabul ettirdik, yanıt durumunun 200 olup olmadığını kontrol ederek ve buna göre hareket ederek bunu daha da genişletebiliriz (bir başarısız işleyici ve benzeri oluşturun). Sorunumuzu etkili bir şekilde çözme.

Hala bunu anlamakta zorlanıyorsanız , MDN'deki AJAX başlangıç ​​kılavuzunu okuyun .


20
"eşzamanlı istekler kodun yürütülmesini engeller ve bellek ve olaylara sızabilir" eşzamanlı istekler belleğe nasıl sızdırabilir?
Matthew G

10
@MatthewG Bu soruya bir ödül ekledim , ne balık tutabileceğimi göreceğim. Alıntıyı bu arada cevaptan kaldırıyorum.
Benjamin Gruenbaum

17
Sadece referans olarak, XHR 2 onloadsadece readyStateolduğu zaman ateş eden işleyiciyi kullanmamıza izin verir 4. Tabii ki IE8'de desteklenmiyor. (iirc, onay gerektirebilir.)
Florian Margaine

9
Anonim bir işlevi geri arama olarak nasıl geçireceğinizle ilgili açıklamanız geçerli ancak yanıltıcıdır. Örnek var bar = foo (); bir değişkenin tanımlanmasını istiyor, oysa önerilen foo (functim () {}); çubuğu tanımlamıyor
Robbie Averill

396

XMLHttpRequest 2 (her şeyden önce Benjamin Gruenbaum ve Felix Kling'in cevaplarını okuyun)

JQuery kullanmıyorsanız ve modern tarayıcılarda ve ayrıca mobil tarayıcılarda çalışan güzel bir kısa XMLHttpRequest 2 istiyorsanız, bu ş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:

  1. Listelenen diğer tüm işlevlerden daha kısadır.
  2. Geri arama doğrudan ayarlanır (böylece ekstra gereksiz kapanma olmaz).
  3. Yeni yükü kullanır (böylece readystate && durumunu kontrol etmeniz gerekmez)
  4. 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 (üç tanesi XMLHttpRequest var adını kullanarak):

En basit:

this.response

Veya herhangi bir nedenden dolayı bind()bir sınıfa geri arama yapıyorsanız :

e.target.response

Misal:

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

Veya (yukarıdakilerden daha iyi olan 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 veya hatta XMLHttpRequest değişken adını kullanmanın daha iyi olduğunu söyleyecektir. Bu yanlış.

Ödeme XMLHttpRequest gelişmiş özelliklerine

Tüm * modern tarayıcıları destekledi. Ve XMLHttpRequest 2 var olduğundan bu yaklaşımı kullandığımı onaylayabilirsiniz. Kullandığım tüm tarayıcılarda hiçbir zaman sorun yaşamadım.

onreadystatechange yalnızca üstbilgileri durum 2'ye almak istiyorsanız kullanışlıdır.

XMLHttpRequestDeğişken adını kullanmak başka bir büyük hatadır, çünkü onback / oreadystatechange kapanışlarında 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ıp 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 tam bir form öğesini ( 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ü bir şey.

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


Yorumda belirtildiği gibi, && senkron hata kullanımı cevap noktasını tamamen bozar. Hangi Ajax'ı doğru şekilde kullanmanın kısa bir yolu var?

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şlevden ödün vermemek için statik olarak tanımlanan bir hata işleyiciniz var. Hata giderici diğer işlevler için de kullanılabilir.

Ama gerçekten bir hata elde etmek için yol yanlış bir URL yazmaktır, bu durumda her tarayıcı bir hata atar.

Özel başlıkları ayarlarsanız, responseType öğesini blob dizi arabelleğine veya başka bir şekilde ayarlarsanız hata işleyicileri yararlı olabilir ...

'POSTAPAPAP' yöntemini yöntem olarak geçseniz bile hata atmaz.

Formdata olarak 'fdggdgilfdghfldj' iletseniz bile hata atmaz.

İlk durumda hata içindedir displayAjax()altındakiler this.statusTextolarakMethod not Allowed .

İkinci durumda, sadece çalışır. Doğru gönderi verisini geçip geçmediğinizi sunucu tarafında kontrol etmeniz gerekir.

etki alanları arası izin verilmiyor hatası otomatik olarak atar.

Hata yanıtında hata kodu yoktur.

Yalnızca this.typehata olarak ayarlanan vardır.

Hatalar üzerinde tamamen kontrolünüz yoksa neden bir hata giderici eklemelisiniz? Hataların çoğu geri arama işlevinde bunun içinde döndürülürdisplayAjax() .

Yani: URL'yi doğru bir şekilde kopyalayıp yapıştırabiliyorsanız hata kontrolüne gerek yoktur. ;)

PS: İ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 . LOL'DİM


Senkronize bir dosyayı okuma

Bunu yapma.

Tarayıcıyı bir süre engellemek istiyorsanız .txtsenkronize 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 olmayan bir şekilde yapmanın başka bir yolu yoktur. (Evet, setTimeout döngüsü ile ... ama cidden?)

Başka bir nokta ... API'lerle veya sadece kendi listenizin dosyalarıyla çalışıyorsanız veya her istek için her zaman farklı işlevler kullanırsanız ...

Yalnızca aynı XML / JSON'u yüklediğiniz bir sayfanız varsa veya yalnızca bir işleve ihtiyacınız olan her şey 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 genişletmek istiyorsanız ...

Evet yapabilirsin.

API'lerin bir sürü kullanıyorum ve her HTML sayfasına entegre ilk işlevlerden biri bu cevapta sadece GET ile ilk Ajax işlevi ...

Ancak XMLHttpRequest 2 ile birçok şey yapabilirsiniz:

Bir indirme yöneticisi (özgeçmiş, filereader, dosya sistemi ile her iki taraftaki aralıkları kullanarak), tuval kullanarak çeşitli resim boyutlandırıcılar dönüştürücüler, web SQL veritabanlarını base64images ile doldurun ve çok daha fazlasını yaptım ... Ama bu durumlarda sadece bunun için bir işlev oluşturmalısınız Amaç ... bazen bir blob, dizi arabelleklerine ihtiyacınız var, üstbilgileri ayarlayabilir, mime türünü geçersiz kılabilir ve çok daha fazlası var ...

Ama buradaki soru, bir Ajax yanıtının nasıl döndürüleceğidir ... (Kolay bir yol ekledim.)


15
Bu cevap güzel olsa da (Ve hepimiz XHR2'yi seviyoruz ve dosya verilerini yayınlamak ve çok parçalı veriler tamamen harika) - bu, XHR'yi JavaScript ile yayınlamak için sözdizimsel şeker gösteriyor - bunu bir blog yayınına koymak isteyebilirsiniz (bunu istiyorum) hatta bir kitaplıkta (değil emin isim hakkında x, ajaxya xhr:) güzel olabilir). Bir AJAX çağrısından yanıt döndürme adresleri nasıl görmüyorum. (birisi hala yapabilir var res = x("url")ve neden işe yaramadığını anlayamaz;)). Bir yan notta - ckullanıcıların kanca errorvb. Böylece yöntemden döndü serin olurdu
Benjamin Gruenbaum

25
2.ajax is meant to be async.. so NO var res=x('url')..Bu soru ve cevapların tamamı bu :)
Benjamin Gruenbaum

3
İlk satırda sahip olduğu değerin üzerine yazıyorsanız, işlevlerde neden bir 'c' parametresi var? bir şey mi kaçırıyorum?
Brian H.

2
Birden çok kez "var" yazmaktan kaçınmak için parametreleri yer tutucu olarak kullanabilirsiniz
cocco

11
@cocco Birkaç tuş vuruşunu kaydetmek için bir SO yanıtına yanıltıcı, okunamayan kod yazdınız mı? Lütfen bunu yapma.
taş

316

Sözler kullanıyorsanız, bu cevap tam size göre.

Bu AngularJS, jQuery (ertelenmiş), yerel XHR'nin değiştirilmesi (getirme), EmberJS, BackboneJS'nin kaydetme veya vaatleri veren herhangi bir düğüm kütüphanesi 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 arama ile jQuery kullanan insanlar için bir cevap yazma iyi bir iş yaptı. Yerli XHR için bir cevabım var. Bu cevap, ön uçta veya arka uçta vaatlerin genel kullanımı içindir.


Temel sorun

Tarayıcıda ve sunucuda NodeJS / io.js içeren JavaScript eşzamanlılık modeli eşzamansız ve reaktiftir .

Bir söz veren bir yöntemi her çağırdığınızda, thenişleyiciler her zaman eşzamansız olarak yürütülür - yani altlarındaki koddan sonra bir .thenişleyicide olmayan.

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ında doğru değere ayarlanmadığı anlamına gelir.datathen

İş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 dataolan undefinedberi data = 5parçası henüz infaz edilmemiştir. Muhtemelen bir saniyede gerçekleşecek, ancak o zamana kadar döndürülen değerle alakasız olacak.

İş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ı elde etmeden önce değeri döndürüyorsunuz.

Bu soruna olası bir çözüm , hesaplama tamamlandığında programınıza ne yapacağını söyleyerek yeniden aktif olarak kodlamaktır . Vaatler, doğada zamansal (zamana duyarlı) olarak aktif olarak bunu mümkün kılar.

Vaatlerde hızlı özet

Bir Vaat, zaman içindeki bir değerdir . Vaatlerin durumu vardır, değersiz beklemeye başlarlar ve aşağıdakilere yerleşebilirler:

  • yerine hesaplama başarıyla tamamlandı anlamına gelir.
  • hesaplama başarısız olduğu için reddetti .

Bir vaat, devletleri yalnızca bir kez değiştirebilir, bundan sonra daima aynı durumda kalır. thenDeğerlerini ayıklama ve hataları işleme vaatlerine işleyiciler ekleyebilirsiniz . thenişleyiciler çağrıların zincirlenmesine izin verir . Vaatler, onları iade eden API'ler kullanılarak oluşturulur . Örneğin, daha modern AJAX değiştirme fetchveya jQuery's$.get geri dönüşü vaat ediyor.

Bir .thensöz verdiğimizde ve ondan bir şey döndüğümüzde - işlenen değer için bir söz alırız . Başka bir söz verirsek inanılmaz şeyler alırız, ama atlarımızı tutalım.

Vaatlerle

Yukarıdaki sorunu vaatlerle nasıl çözebileceğimizi görelim. İlk olarak, bir gecikme fonksiyonu oluşturmak için Promise yapıcısını kullanarak yukarıdaki 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 kullanmak thenüzere dönüştürdükten sonra, 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. Açabileceğiniz bir kutu gibi then.

Bunu uygulamak

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 işe yarıyor. Zaten eşzamansız çağrılardan değer döndüremeyeceğimizi öğrendik, ancak vaatleri kullanabilir ve işlem yapmak için onları zincirleyebiliriz. Artık eşzamansız bir çağrıdan yanıtın nasıl döndürüleceğini biliyoruz.

ES2015 (ES6)

ES6 , ortada dönebilen ve daha sonra bulundukları noktaya devam edebilen işlevler olan jeneratörleri sunar . Bu genellikle diziler için yararlı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 sekans üzerinden yineleyici döndüren bir işlevdir 1,2,3,3,3,3,..... Bu kendi başına ilginç olsa da, birçok olasılık için yer açıyor olsa da, ilginç bir durum var.

Ürettiğimiz dizi sayılar yerine bir dizi işlemse - bir eylem gerçekleştiğinde işlevi duraklatabilir ve işlevi sürdürmeden önce bekleyebiliriz. Yani bir sayı dizisi yerine, bir gelecek dizisine ihtiyacımız var değerlerin - yani: vaatler.

Bu biraz zor ama çok güçlü hile eşzamansız olarak eşzamansız kod yazmamızı sağlar. Bunu sizin için yapan birkaç "koşucu" vardır, bir tane yazmak kısa bir kod satırıdır, ancak bu cevabın kapsamı dışındadır. Promise.coroutineBurada Bluebird'i kullanacağım , ancak coveya gibi başka paketleyiciler de 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 koroutinlerden tüketebileceğimiz bir söz verir. Ö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, ancak hepsinde awaitsöz verebilirsiniz . Bu ekleyerek yukarıda ES6 öneriyi sadece "şeker" (güzel söz dizimi) 'dir asyncve awaitanahtar kelimeleri. 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
}

Hala aynı sözü veriyor :)


Bu kabul edilen cevap olmalı. Async / await için +1 (olmamalı return await data.json();mı?)
Lewis Donovan

247

Ajax'ı yanlış kullanıyorsunuz. Fikir, herhangi bir şey döndürmesini değil, verileri geri arama işlevi adı verilen ve verileri işleyen bir şeye teslim etmesidir.

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şleyicisinde bir şey döndürmek hiçbir şey yapmaz. Bunun yerine, ya verileri teslim etmeli ya da onunla istediğiniz şeyi doğrudan başarı işlevi içinde yapmalısınız.


13
Bu cevap tamamen anlamsaldır ... başarı yönteminiz sadece bir geri arama içindeki bir geri aramadır. Sadece sahip olabilirsin success: handleDatave işe yarayacaktı.
Jacques ジ ャ ッ ク

5
Peki ya "handleData" dışında "handleData" dışında dönmek isterseniz ... :) ... nasıl yapacaksınız ...? ... basit bir dönüş neden ajax "başarı" geri arama dönecektir ... ve "handleData" dışında değil ...
pesho hristov

@Jacques & @pesho hristov Bu noktayı kaçırdınız. Gönder işleyicisi successyöntem değil , çevresi kapsamı $.ajax.
travnik

@travnik Bunu kaçırmadım. HandleData içeriğini aldı ve başarı yöntemine koyduysanız, tamamen aynı davranır ...
Jacques ジ ャ ッ ク

234

En basit çözüm bir JavaScript işlevi oluşturmak ve Ajax successgeri çağırma 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);    
}); 

3
Kimin olumsuz oy verdiğini bilmiyorum. Ama bu etrafında çalışmış bir çalışma aslında ben bu yaklaşımı bütün bir uygulama oluşturmak için kullandım. Jquery.ajax veri döndürmez, bu nedenle yukarıdaki yaklaşımı kullanmak daha iyidir. Yanlışsa, lütfen açıklayın ve daha iyi bir yol önerin.
Hemant Bavle

11
Üzgünüm, yorum bırakmayı unuttum (genellikle yaparım!). İndirdim. Downvotes gerçek doğruluğunu veya eksikliğini göstermez, bağlamında veya eksikliğinde faydalı olduğunu gösterir. Bunu çok daha ayrıntılı olarak açıklayan Felix'in göz önüne alındığında cevabınızı yararlı bulmuyorum. Bir yan notta, JSON ise yanıtı neden dizginleyesiniz?
Benjamin Gruenbaum

5
ok .. @Benjamin i bir JSON Nesnesi dizeye dönüştürmek için stringify kullanılır. Ve amacınızı açıkladığınız için teşekkürler. Daha ayrıntılı cevaplar yayınlamayı unutmayın.
Hemant Bavle

Peki ya "responseObj" i "successCallback" dışında iade etmek isterseniz ... :) ... nasıl yapacaksınız ...? ... çünkü basit bir geri dönüş ajax'ın "başarı" geri aramasına dönecektir ... ve "successCallback" dışında değil ...
pesho hristov

221

Korkunç görünümlü, elle çizilmiş bir çizgi romanla cevap vereceğim. İkinci resim nedeni budur resultolduğunu undefinedsizin kod örneğinde.

resim açıklamasını buraya girin


32
Bir resim bin kelimeye bedel olduğunu , A Kişisi - dönüş içinde, arabasını düzeltmek için 's kişi B detaylarını sor Kişi B - araba yanıtı alındığında, ayrıntıları sabitleme için sunucudan yanıt Ajax Çağrısı bekler yapar, Ajax Başarı işlevi Kişi çağırır B fonksiyonu ve yanıtı argüman olarak iletir, A kişisi cevabı alır.
shaijut

10
Kavramları göstermek için her görüntüye kod satırları eklerseniz harika olur.
Hassan Baig

1
Bu arada, arabalı adam yolun kenarında sıkışmış. O gerektirir araba devam etmeden önce sabittir. Artık yolun kenarında yalnız bekliyor ... Durum değişikliklerini beklemek için telefonda olmayı tercih ediyordu ama tamirci bunu yapmazdı ... Tamirci, işine devam etmesi gerektiğini ve yapamayacağını söyledi sadece telefonda takılmak. Mekanik onu mümkün olan en kısa sürede geri arayacağına söz verdi. Yaklaşık 4 saat sonra adam pes eder ve Uber'i arar. - Zaman aşımı örneği.
barrypicker

@barrypicker :-D Parlak!
Johannes Fahrenkrug

159

Angular1

AngularJS kullanan insanlar için, bu durumu kullanarak başa çıkabilir Promises.

İşte diyor ki,

Vaatler, eşzamansız asenkron fonksiyonları kullanmak için kullanılabilir ve birden fazla fonksiyonu birlikte zincirlemenizi sağlar.

Burada da güzel bir açıklama bulabilirsiniz .

Örnek aşağıda belirtilen dokümanlarda bulundu .

  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.

Açısal2 ve Sonrası

In Angular2aşağıdaki örnekte bakmak, ama onun ile önerilen kullanımına Observablessahip 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));
}

Buradaki orijinal gönderiye bakın . Ancak Typescript yerel es6 Vaatlerini desteklemez , kullanmak istiyorsanız bunun için eklentiye ihtiyacınız olabilir.

Ayrıca burada spec tanımlanmış vaatler .


15
Bu, vaatlerin bu sorunu nasıl çözeceğini açıklamıyor.
Benjamin Gruenbaum

4
jQuery ve fetch yöntemlerinin her ikisi de vaatlerde bulunur. Cevabınızı gözden geçirmenizi öneririm. Rağmen jQuery's aynı değil (o zaman orada, ama yakalamak değil).
İzleyici1

153

Buradaki yanıtların çoğu, tek bir zaman uyumsuz işleminiz olduğunda yararlı öneriler sunar, ancak bazen, bir dizideki veya liste benzeri bir yapıdaki her giriş için zaman uyumsuz bir işlem yapmanız gerektiğinde ortaya çıkar . Günaha bunu yapmaktır:

// 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:

İşe yaramaz olmasının nedeni doSomethingAsync, sonuçları kullanmaya çalıştığınız andan itibaren gelen geri çağrıların henüz çalışmamış olmasıdır .

Bu nedenle, bir diziniz (veya bir tür listeniz) varsa ve her giriş için zaman uyumsuz işlemler yapmak istiyorsanız, iki seçeneğiniz vardır: İşlemleri paralel (üst üste binme) veya seri olarak (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:

(Yapabiliriz expectingve sadece kullanabiliriz results.length === theArray.length, ama bu bizitheArray aramalar olağanüstü iken değişen ...)

Uyarı kullandığımız nasıl indexelde forEachsonucu kaydetmek içinresultsSonuçları düzensiz gelse bile, sonucu ilgili girdiyle aynı konumda (zaman uyumsuz çağrılar başlatıldıkları sırayla tamamlanmayabilir).

Ancak bu sonuçları bir işlevden döndürmeniz gerekirse ne olur ? Diğer cevapların işaret ettiği gibi yapamazsınız; işlevinizin bir geri çağrıyı kabul etmesi ve çağırması (veya bir Söz vermeniz ) gerekir. İşte 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:

Ya da Promisebunun yerine dönen 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);
});

Tabii ki, doSomethingAsyncbize hatalar geçtiyse,reject bir hata aldığımızda vaadi reddetmek için kullanırdık.)

Misal:

(Ya da alternatif olarak, bunun için doSomethingAsyncbir söz döndüren bir sarıcı yapabilir ve ardından aşağıdakileri yapabilirsiniz ...)

Eğer doSomethingAsyncsize 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);
});

doSomethingAsyncBunun ikinci ve üçüncü bir argümanı yok sayacağını biliyorsanız, bunu doğrudan doğrudan aktarabilirsiniz map( mapgeri çağrısını üç argümanla çağırır, ancak çoğu insan yalnızca çoğu zaman ilkini kullanır):

function doSomethingWith(theArray) {
    return Promise.all(theArray.map(doSomethingAsync));
}
doSomethingWith(theArray).then(function(results) {
    console.log("Results:", results);
});

Misal:

Not Promise.allsözünü hepsi çözüldükten sonra bunu vermek sözlerin hepsi sonuçlarının bir dizi sözünü giderir veya reddeder zaman ilk bunu reddeder vermek sözlerin.

Dizi

İşlemlerin paralel olmasını istemediğinizi varsayalım? Bunları birbiri ardına çalıştırmak istiyorsanız, bir sonraki işleme başlamadan önce her işlemin tamamlanmasını beklemeniz gerekir. İşte bunu yapan ve sonuç ile geri arama çağıran bir fonksiyon ö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, sonuçları sıradan results.push(result)almayacağımızı bildiğimizden beri kullanabiliriz . Yukarıda kullanabilirdik results[index] = result;, ancak aşağıdaki örneklerin bazılarında bir dizinimiz yok kullanmak.)

Misal:

(Ya da, yine, doSomethingAsyncsize bir söz veren ve aşağıdakileri yapan bir sarıcı yapın ...)

Eğer doSomethingAsyncsize 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:

ES2017 + sözdizimini (henüz) kullanamıyorsanız, "Promise azaltma" deseninde bir varyasyon kullanabilirsiniz (bu, normal Promise azaltmasından daha karmaşıktır, çünkü sonucu birinden diğerine geçirmiyoruz, 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:

... ES2015 + ok fonksiyonları ile daha az kullanışlıdır :

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:


1
if (--expecting === 0)Kodun bir kısmının nasıl çalıştığını açıklar mısınız? Çözümünüzün geri arama sürümü benim için harika çalışıyor, bu ifadeyle, tamamlanan yanıtların sayısını nasıl kontrol ettiğinizi anlamıyorum. Benim açımdan sadece bilgi eksikliği takdir. Çekin yazılmasının alternatif bir yolu var mı?
Sarah

@Sarah: expectingdeğeri ile başlıyor array.length, kaç tane talep yapacağız. Tüm bu talepler başlayana kadar geri aramanın çağrılmayacağını biliyoruz. Geri if (--expecting === 0)aramada şunları yapar: 1. Azalmalar expecting(bir yanıt aldık, bu yüzden bir tane daha az yanıt bekliyoruz) ve azalmadan sonraki değer 0 ise (daha fazla yanıt beklemiyoruz), bitmiş!
TJ Crowder

1
@PatrickRoberts - Teşekkürler !! Evet, kopyala-yapıştır hatası, bu ikinci argüman bu örnekte tamamen göz ardı edildi (belirttiğinizden beri başarısız olmasının tek nedeni budur results). :-) Onu düzeltti.
TJ Crowder

111

Bu ö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 getJokeolan bir dönen çözülmesi söz (döndürürken çözülene res.data.value). Bu yüzden $ http.get isteği tamamlanana ve sonra console.log (res.joke) yürütülene kadar (normal bir asenkron akış olarak ) beklersiniz .

Bu plnkr:

http://embed.plnkr.co/XlNR7HpCaIhJxskMJfSg/

ES6 yolu (zaman uyumsuz - bekliyor)

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

107

Bu, veri bağlama veya depolama kavramının iki yolunun bulunduğu yerlerden biridir. birçok yeni JavaScript çerçevesinde kullanılan sizin için harika çalışacağı ...

Bu nedenle , açısal, React veya veri bağlama veya depolama kavramını iki şekilde yapan başka bir çerçeve kullanıyorsanız , bu sorun sizin için basitçe düzeltilmiştir, bu yüzden kolay bir deyişle undefined, sonuç ilk aşamadadır,result = undefined aldığınız önce verileri alırsanız, sonucu alır almaz güncellenir ve Ajax çağrınızın yanıtını veren yeni değere atanır ...

Ama saf javascript veya jQuery'de nasıl yapabilirsiniz? bu soruyu sorduğunuz gibi ?

Bir kullanabilir geri arama , söz son zamanlarda ve gözlemlenebilir biz gibi bazı işlevlere sahip vaatlerinde örneğin, sizin için işlemek için success()veya then()hangi veri, sizin için hazır geri arama ile aynıdır veya çalıştırılacaktır abone üzerinde fonksiyonunu gözlemlenebilir .

Örneğin , jQuery kullandığınız durumda , bö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
});

Daha fazla bilgi için, bu asenkron maddeleri yapmanın yeni yolları olan vaatler ve gözlemlenebilirler hakkında çalışın.


Bu, küresel kapsamda iyidir, ancak bazı modül bağlamında, muhtemelen geri arama için doğru bağlamı sağlamak istersiniz, örneğin$.ajax({url: "api/data", success: fooDone.bind(this)});
steve.sims

8
React tek yönlü veri bağlayıcı olduğundan bu aslında yanlıştır
Matthew Brent

@MatthewBrent yanlış değil, aynı zamanda doğru değil, React sahne nesne ve değiştirilirse, uygulama boyunca değişir, ancak React geliştiricisinin bunu tavsiye ettiği bir yol değil ...
Alireza

98

JavaScript'in “gizemleri” ile mücadele ederken karşılaştığımız çok yaygın bir konudur. Bugün bu gizemi belirsizleştirmeye çalışayım.

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 (burada her kod satırı bir sonraki sıradan önce 'işi ile bitirilir') ve sonuç beklendiği gibi aynıdır.

Şimdi, fonksiyonumuza çok az gecikme ekleyerek, biraz kod ekleyelim, böylece tüm kod satırları sırayla 'bitirilmez'. Böylece, fonksiyonun asenkron davranışını taklit eder:

function foo(){
 setTimeout( ()=>{
   return 'wohoo';
  }, 1000 )
}

let bar = foo() // bar is undefined here

İşte bu gecikme beklediğimiz işlevselliği bozdu! Ama tam olarak ne oldu? Eğer koda bakarsanız aslında oldukça mantıklı. işlev foo()yürütüldükten sonra hiçbir şey döndürmez (bu nedenle döndürülen değer olur undefined), ancak 'wohoo' döndürmek için 1 saniye sonra bir işlevi yürüten bir zamanlayıcı başlatır. Ama gördüğünüz gibi, bara atanan değer foo () 'dan derhal döndürülen şeylerdir, yani sadece bir şey değildir undefined.

Peki, bu sorunu nasıl çözeceğiz?

Fonksiyonumuzdan bir SÖZ isteyelim . Söz gerçekten ne anlama geldiğiyle ilgilidir: fonksiyon, gelecekte alacağı herhangi bir çıktıyı sağlamanızı garanti eder. bu yüzden 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'
});

Böylece, özet - ajax tabanlı çağrılar vb gibi asenkron işlevlerle başa çıkmak için resolve, değere ( söz vermek istediğiniz) bir vaat kullanabilirsiniz . Bu nedenle kısaca size çözmek yerine değerini dönen asenkron fonksiyonlarda.

GÜNCELLEME (Zaman uyumsuz / beklemede vaat ediyor)

Vaatlerle then/catchçalışmak için kullanmanın dışında bir yaklaşım daha var. Fikir etmektir zaman uyumsuz bir işlevi tanımak ve sonra vaat bekleyin kod sonraki satıra geçmeden önce, gidermek için. Hala sadece promiseskaputun 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:

sonra / catch sürümü:

function saveUsers(){
     getUsers()
      .then(users => {
         saveSomewhere(users);
      })
      .catch(err => {
          console.error(err);
       })
 }

zaman uyumsuz / beklenen sürüm:

  async function saveUsers(){
     try{
        let users = await getUsers()
        saveSomewhere(users);
     }
     catch(err){
        console.error(err);
     }
  }

bu hala bir vaatten veya asenkron / beklemeden bir değer döndürmenin en iyi yolu olarak görülüyor mu?
edwardsmarkf

3
@edwardsmarkf Şahsen böyle iyi bir yol olduğunu düşünmüyorum. O zaman / catch, async / await yanı sıra benim kod asenkron bölümleri için jeneratörler ile vaatler kullanın. Bu büyük ölçüde kullanım bağlamına bağlıdır.
Anish K.

96

Eşzamansız bir işlevden bir değer döndürmek için başka bir yaklaşım, sonucu eşzamansız işlevden saklayacak bir nesneye 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');
});

Kullanıyorum resultNesneyi eşzamansız işlem sırasında değeri saklamak için . Bu, sonucun eşzamansız işten sonra bile kullanılabilir olmasını sağlar.

Bu yaklaşımı çok kullanıyorum. Sonucun ardışık modüller aracılığıyla yeniden kablolandırılması söz konusu olduğunda bu yaklaşımın ne kadar iyi çalıştığını bilmek isterim.


9
Burada bir nesne kullanma konusunda özel bir şey yok. Eğer doğrudan cevap vermiş olsaydınız da işe yarayacaktır result. Async işlevi tamamlandıktan sonra değişkeni okuduğunuz için çalışır .
Felix Kling

85

Vaatler ve geri çağrılar birçok durumda iyi çalışır, ancak arkada bir şey ifade etmek acı verici:

if (!name) {
  name = async1();
}
async2(name);

Sonuçta sen geçeceksin async1; nameundefined olup olmadığını kontrol edin ve buna göre geri aramayı çağırı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 sorunun çözülmesine 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 .


1
@recurf - Bu benim projem değil. Sorun izleyicisini kullanmayı deneyebilirsiniz.
rohithpr

1
bu jeneratör fonksiyonlarına benzer mi? developer.mozilla.org/tr-TR/docs/Web/JavaScript/Reference/… *
Emanegux

1
Bu hala alakalı mı?
Aluan Haddad

Sen yararlanabilir async-awaitEğer düğümün en yeni versiyonlarının bazı kullanıyorsanız. Birisi eski sürümlere takılı kalırsa bu yöntemi kullanabilir.
18:18 de rohithpr

83

Yazdığım aşağıdaki örnek,

  • Zaman uyumsuz HTTP çağrılarını yönetme;
  • Her bir API çağrısından yanıt bekleyin;
  • Promise desenini kullanın ;
  • Birden fazla HTTP çağrısına katılmak için Promise.all kalıbını kullanın ;

Bu çalışma örneği müstakildir. XMLHttpRequestArama yapmak için pencere nesnesini kullanan basit bir istek nesnesi tanımlar . Bir grup vaatin tamamlanmasını beklemek için basit bir işlev tanımlayacaktır.

Bağlam. Örnek, belirli bir sorgu dizeleri kümesi için nesneleri aramak amacıyla Spotify Web API uç noktasını playlistsorgular:

[
 "search?type=playlist&q=%22doom%20metal%22",
 "search?type=playlist&q=Adele"
]

Her öğe için yeni bir Promise bir blok ExecutionBlocktetikler - sonucu ayrıştırır, sonuç dizisine dayalı yeni bir vaat kümesi planlar, yani Spotify usernesnelerinin listesi ve yeni HTTP çağrısını ExecutionProfileBlockeşzamansız olarak yürütür .

Daha sonra, birden çok ve tamamen eşzamansız iç içe HTTP çağrısı oluşturmanıza ve her bir çağrı alt kümesinin sonuçlarına katılmanıza olanak tanıyan iç içe bir Promise yapısı görebilirsiniz Promise.all.

NOT Son Spotify searchAPI'ları, 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ırmak için erişim kodunuzu 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 çok tartıştım .


80

Kısa cevap, şöyle bir geri arama uygulamanız gerektiğidir:

function callback(response) {
    // Here you can do what ever you want with the response object.
    console.log(response);
}

$.ajax({
    url: "...",
    success: callback
});

78

2017 yanıtı: Artık her mevcut tarayıcıda ve düğümde tam olarak istediğinizi yapabilirsiniz

Bu oldukça basit:

  • Bir Söz Ver
  • JavaScript'e bir değere (HTTP yanıtı gibi) çözümleneceği vaadini beklemesini söyleyecek 'bekliyor' komutunu kullanın
  • 'Async' anahtar sözcüğünü üst işleve ekleyin

İşte kodunuzun çalışan bir sürümü:

(async function(){

var response = await superagent.get('...')
console.log(response)

})()

await, mevcut tüm tarayıcılarda ve düğüm 8'de desteklenir


7
Ne yazık ki, bu sadece vaatleri veren işlevlerle çalışır - örneğin geri arama kullanan Node.js API ile çalışmaz. Ve Babil olmadan kullanmanızı tavsiye etmem, çünkü herkes "mevcut tarayıcıları" kullanmaz.
Michał Perłakowski

2
@ MichałPerłakowski düğümü 8 , node.js API'sı iade vaatleri yapmak için kullanılabilen nodejs.org/api/util.html#util_util_promisify_original içerir . Mevcut olmayan tarayıcıları desteklemek için zamanınız ve paranız olup olmadığı durumunuza bağlıdır.
mikemaccana

IE 11 2018'de hala mevcut bir tarayıcı, ne yazık ki ve desteklemiyorawait/async
Juan Mendes

IE11 geçerli bir tarayıcı değil. 5 yıl önce piyasaya sürüldü, caniuse göre dünya çapında% 2,5 pazar payına sahip ve birisi mevcut tüm teknolojiyi görmezden gelmek için bütçenizi iki katına çıkarmazsa, çoğu insanın zamanına değmez.
mikemaccana

76

Js tek iş parçacıklıdır.

Tarayıcı üç bölüme ayrılabilir:

1) Olay Döngüsü

2) Web API'sı

3) Olay Sırası

Event Loop sonsuza dek çalışır, yani sonsuz döngü türdür.Our Queue, tüm fonksiyonunuzun bir olaya itildiği yerdir (örnek: click), bu sırayla gerçekleştirilen ve bu işlevi yürüten ve kendi kendine hazırlayan Event loop'a yerleştirilir. Bu, bir işlevin yürütülmesi, olay döngüsünde sıradaki işlev yürütülene kadar başlatılmayacağı anlamına gelir.

Şimdi bir sunucuda bir veri almak için bir sıradaki iki işlevi ittiğimizi düşünelim ve diğeri bu verileri kullanıyor. Önce serverRequest () işlevini sonra kuyrukta utiliseData () işlevini ittik. serverRequest işlevi olay döngüsüne gider ve sunucudan veri almanın ne kadar zaman alacağını asla bilemeyeceğimiz için sunucuya bir çağrı yapar, bu nedenle bu işlemin zaman alması beklenir ve bu nedenle olay döngümüzle meşgul oluruz, böylece sayfamızı asarız. API, bu işlevi olay döngüsünden alır ve sıradan bir sonraki işlevi yürütebilmemiz için sunucu döngüsünü serbest hale getirir. Sıradaki bir sonraki işlev, döngüde bulunan utiliseData () işlevidir, ancak kullanılabilir veri olmadığı için gider Atık ve sonraki işlevin yürütülmesi kuyruğun sonuna kadar devam eder. (Buna Async çağrısı denir, yani veri alana kadar başka bir şey yapabiliriz)

Diyelim ki serverRequest () fonksiyonumuzun kodda bir return ifadesi olduğunu varsayalım, sunucu Web API'sından veri geri aldığımızda kuyruğun sonunda kuyruğa girer. Kuyrukta sonuna kadar itildikçe, bu verileri kullanmak için kuyruğumuzda kalan hiçbir işlev olmadığından, verilerini kullanamayız. Bu nedenle Async Call'tan bir şey döndürmek mümkün değildir.

Dolayısıyla bunun çözümü geri arama veya vaattir .

Buradaki cevaplardan bir görüntü, Geri arama kullanımını doğru bir şekilde açıklıyor ... İşlevimizi (sunucudan döndürülen verileri kullanan işlev) işlev çağıran sunucuya veriyoruz.

Geri aramak

 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 şöyle denir:

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


68

Uzaktan arama yapmak için bu özel kütüphaneyi (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)
});

67

Başka bir çözüm, sıralı yürütücü nsynjs üzerinden kod yürütmektir .

Temel işlev söz konusuysa

nsynjs tüm vaatleri sırayla değerlendirecek ve vaat sonucunu datamülk haline getirecektir :

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 çağırma işlevini nsynjs uyumlu sarmalayıcıya sarın (vaat edilmiş bir sürümü varsa, 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. Eşzamanlı mantığı işleve yerleştirin:

function process() {
    console.log('got data:', ajaxGet(nsynjsCtx, "data/file1.json").data);
}

Adım 3. nsynjs üzerinden senkronize olarak fonksiyonu çalıştırın:

nsynjs.run(process,this,function () {
    console.log("synchronous function finished");
});

Nsynjs, yavaş işlevin bir sonucunun hazır olmaması durumunda yürütmeyi duraklatarak tüm işleçleri ve ifadeleri adım adım değerlendirir.

Burada daha fazla örnek: https://github.com/amaksr/nsynjs/tree/master/examples


2
Bu ilginç. Eşzamansız çağrıları diğer dillerde yaptığınız gibi kodlamayı nasıl sağladığı hoşuma gidiyor. Ancak teknik olarak gerçek JavaScript değil mi?
J Morris

41

ECMAScript 6, senkronize olmayan bir tarzda kolayca programlamanızı sağlayan 'jeneratörlere' 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 bunu 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 oluşturmak için kodu Babel veya closure-compiler aracılığıyla çalıştırabilirsiniz.

Geri arama ...argsbir diziye sarılır ve okuduğunuzda imha edilir, böylece desen birden çok argümanı olan geri aramalarla başa çıkabilir. Örneğin fs düğümü ile :

const [err, data] = yield fs.readFile(filePath, "utf-8", callback);

39

Eşzamansız isteklerle çalışmaya yönelik bazı yaklaşımlar şunlardır:

  1. Tarayıcı Promise nesnesi
  2. S - JavaScript için bir vaat kütüphanesi
  3. A + Promises.js
  4. jQuery ertelendi
  5. XMLHttpRequest API'sı
  6. Geri arama kavramını kullanma - İlk cevapta uygulama olarak

Örnek: jQuery, birden çok istekle çalışmak için uygulamayı 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();


38

Kendimizi "zaman" dediğimiz bir boyut boyunca ilerleyen bir evrende buluyoruz. Zamanın ne olduğunu gerçekten anlamıyoruz, ama bunun hakkında akıl yürütmemize ve konuşmamıza izin veren soyutlamalar ve kelime dağarcığı geliştirdik: "geçmiş", "şimdiki", "gelecek", "önce", "sonra".

İnşa ettiğimiz bilgisayar sistemlerinin - giderek daha önemli - önemli bir boyut olarak zamanı var. Gelecekte bazı şeyler olacak şekilde ayarlanmıştır. O zaman, ilk şeylerin ortaya çıkmasından sonra başka şeylerin olması gerekir. Bu "asenkroniklik" olarak adlandırılan temel kavramdır. Giderek daha fazla ağa bağlanmış dünyamızda, en yaygın eşzamansızlık durumu, bazı uzak sistemlerin bazı isteklere yanıt vermesini beklemektedir.

Bir örnek düşünün. Sütçü diyorsun ve biraz süt ısmarlıyorsun. Gelince, 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 yaramaz:

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, o bilmiyor order_milkolan asenkron gelecekteki bir süre kadar sütün neden gitmiyor --is şey. JS ve diğer bildirim dilleri, bir ifadeyi beklemeden yürütür.

Bu soruna klasik JS yaklaşımı, JS'nin işlevleri iletilebilen birinci sınıf nesneler olarak desteklemesinden faydalanarak, bir işlevi asenkron talebe bir parametre olarak bir işlev olarak iletmektir. görevi bir ara gelecekte. Bu "geri arama" yaklaşımıdır. Şöyle görünüyor:

order_milk(put_in_coffee);

order_milkbaşlıyor, sütü emrediyor, sonra, sadece ve ne zaman geldiğini çağırıyor put_in_coffee.

Bu geri çağırma yaklaşımındaki sorun, sonucunu bildiren bir fonksiyonun normal anlambilimini kirletmesidir return; bunun yerine, işlevler parametre olarak verilen bir geri çağrı çağırarak sonuçlarını raporlamamalıdır. Ayrıca, daha uzun olay dizileriyle uğraşırken bu yaklaşım hızla kötüleşebilir. Örneğin, sütün kahveye konmasını beklemek istediğimi ve sonra sadece üçüncü bir adımda kahve içmeyi istediğimi varsayalım. Sonunda böyle bir şey yazmak gerek sonunda:

order_milk(function(milk) { put_in_coffee(milk, drink_coffee); }

put_in_coffeehem sütü içine koymak için hem de sütü koyduktan sonra drink_coffeeyürütme eylemine ( ) geçiyorum . Bu kodun yazılması, okunması ve hata ayıklanması 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 tür gelecekteki veya eşzamansız bir sonucu temsil eden belirli bir değer türü olan bir "vaat" kavramının motivasyonuydu . Hali hazırda gerçekleşen ya da gelecekte gerçekleşecek ya da hiç gerçekleşmeyecek bir şeyi temsil edebilir. Vaatlerin then, sözün temsil ettiği sonuç gerçekleştiğinde yürütülecek bir eylemi geçtiğiniz tek bir yöntemi vardır.

Süt ve kahvemiz durumunda, order_milkgelen süt için bir söz vermek için tasarlıyoruz , daha sonra put_in_coffeebir theneylem olarak belirtiyoruz:

order_milk() . then(put_in_coffee)

Bunun bir avantajı, gelecekteki olayların ("zincirleme") dizileri oluşturmak için bunları bir araya getirebilmemizdir:

order_milk() . then(put_in_coffee) . then(drink_coffee)

Sorununuza söz verelim. Talep mantığımızı bir söz veren bir fonksiyonun içine koyacağız:

function get_data() {
  return $.ajax('/foo.json');
}

Aslında, yaptığımız tek şey returnçağrıya bir eklenir $.ajax. Bu çalışır çünkü jQuery $.ajaxzaten bir tür söz benzeri şey döndürür. (Uygulamada, ayrıntılara girmeden, gerçek bir söz vermek için bu çağrıyı sarmayı veya buna bir alternatif kullanmayı tercih $.ajaxederiz.) Şimdi, dosyayı yüklemek ve bitmesini beklersek ve sonra bir şeyler yapın,

get_data() . then(do_something)

Örneğin,

get_data() . 
  then(function(data) { console.log(data); });

Sözlerini kullanırken, içine fonksiyonları çok geçen sona thendaha kompakt ES6 tarzı ok işlevlerini kullanmak genellikle yararlı olur, böylece:

get_data() . 
  then(data => console.log(data));

asyncanahtar kelime

Ancak, eşzamanlı olarak kod tek yönlü ve eşzamansız ise oldukça farklı bir yol yazmaktan hala belirsiz bir şey var. Senkronize için yazıyoruz

a();
b();

ama aasenkron ise, vaatlerle yazmak zorundayız

a() . then(b);

Yukarıda, "JS'nin, ikinci çağrıyı gerçekleştirmeden önce ilk çağrının bitmesini beklemesi gerektiğini bilmenin bir yolu yok" dedik . Orada eğer hoş olmaz idi o JS anlatmak için bir yol? Anlaşılan o ki, await"asenkron" işlevi adı verilen özel bir işlev içinde kullanılan anahtar sözcük. Bu özellik, ES'nin gelecek sürümünün bir parçasıdır, ancak doğru hazır ayarlar verildiğinde Babel gibi transpillerde zaten mevcuttur. Bu sadece yazmamızı sağlar

async function morning_routine() {
  var milk   = await order_milk();
  var coffee = await put_in_coffee(milk);
  await drink(coffee);
}

Sizin durumunuzda şöyle bir şey yazabilirsiniz:

async function foo() {
  data = await get_data();
  console.log(data);
}

37

Kısa cevap : foo()Yöntem işlev döndükten sonra$ajax() çağrı zaman uyumsuz olarak yürütülürken yönteminiz hemen geri döner . Sorun o zaman zaman uyumsuz çağrı tarafından alınan sonuçları nasıl veya nerede saklanır döndükten sonra.

Bu iş parçacığında çeşitli çözümler verilmiştir. Belki de en kolay yol, bir nesneyi foo()yönteme geçirmek ve sonuçları zaman uyumsuz çağrı tamamlandıktan sonra 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

İçin yapılan çağrının foo()yine de yararlı bir şey döndürmeyeceğini unutmayın . Ancak, zaman uyumsuz çağrının sonucu artık depolanacaktır result.response.


14
Bu işe yararken, küresel bir değişkene atamaktan daha iyi değildir.
Felix Kling

36

Başarı callback()içinde bir işlev kullanın foo(). Bu şekilde deneyin. Basit ve anlaşılması 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();

29

Soru şuydu:

Eşzamansız bir çağrıdan yanıtı nasıl geri gönderebilirim?

ki bunlar şu şekilde yorumlanabilir:

Eşzamansız kod nasıl senkronize hale getirilir ?

Çözüm, geri aramalardan kaçınmak ve bir Promises ve async / await kombinasyonu kullanmak olacaktır .

Ajax talebi için bir örnek vermek istiyorum.

(Javascript ile yazılabilse de, Python'a yazmayı ve Transcrypt kullanarak Javascript'e derlemeyi tercih ederim . Yeterince açık olacak.)

Önce JQuery kullanımını etkinleştirelim, şu şekilde $kullanılabilir olsun S:

__pragma__ ('alias', 'S', '$')

Promise döndüren bir işlevi , bu durumda bir Ajax çağrısını tanımlayın :

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

29

Promise Kullanımı

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...!

Vaat kullanma konusunda 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 eğer tanımlanmamışsa js derleyicilerinin altındaki ES3 için kendi Promise sınıfımı uygulamaya karar verdim . Bu kodu ana kodunuzdan önce ekleyin ve sonra Promise!

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

28

Tabii ki senkronize 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. Kod snippet'iniz biraz farklı yeniden yazılabilir:

function foo() {
    var result;

    $.ajax({
        url: '...',
        success: function(response) {
            myCallback(response);
        }
    });

    return result;
}

function myCallback(response) {
    // Does something.
}

5
Geri aramalar veya JavaScript hakkında doğası gereği zaman uyumsuz bir şey yoktur.
Aluan Haddad

19

Size kod atmak yerine, JS'nin geri çağrıları ve eşzamansızlığı nasıl ele aldığını anlamanın anahtarı olan 2 kavram vardır. (bu bir kelime mi?)

Etkinlik Döngüsü ve Eşzamanlılık Modeli

Dikkat etmeniz gereken üç şey vardır; Kuyruk; olay döngüsü ve yığın

Geniş ve basit terimlerle, olay döngüsü proje yöneticisi gibidir, sürekli çalışmak ve sıra ile yığın arasında iletişim kurmak isteyen işlevleri dinler.

while (queue.waitForMessage()) {
   queue.processNextMessage();
}

Bir şeyi çalıştırmak için bir mesaj aldığında, kuyruğa ekler. Sıra, yürütmeyi bekleyen şeylerin listesidir (AJAX isteğiniz gibi). şöyle düşünün:

 1. call foo.com/api/bar using foobarFunc
 2. Go perform an infinite loop
 ... and so on

Bu iletilerden biri yürütülecekse iletiyi kuyruktan çıkarır ve bir yığın oluşturur; yığın, JS'nin iletideki komutu gerçekleştirmek için yürütmesi gereken her şeydir. Yani bizim örneğimizde,foobarFunc

function foobarFunc (var) {
  console.log(anotherFunction(var));
}

Yani foobarFunc'un yürütmesi gereken her şey (bizim durumumuzda anotherFunction) yığına itilecek. yürütülür ve unutulur - olay döngüsü daha sonra kuyruktaki bir sonraki öğeye geçecektir (veya iletileri dinleyecektir)

Buradaki anahtar şey icra emridir. Yani

NE ZAMAN koşacak

Harici bir tarafa AJAX kullanarak arama yaptığınızda veya herhangi bir eşzamansız kodu (örneğin bir setTimeout) çalıştırdığınızda, Javascript devam etmeden önce bir yanıta bağımlıdır.

Büyük soru ne zaman cevap alacak? Cevap bilmiyoruz - bu yüzden olay döngüsü bu iletinin "hey run me" demenizi bekliyor. JS senkronize olarak bu mesajı beklediyse uygulamanız donacak ve emilecek. JS, mesajın kuyruğa geri eklenmesini beklerken sıradaki bir sonraki öğeyi yürütmeye devam eder.

Bu yüzden eşzamansız işlevsellik ile geri arama adı verilen şeyleri kullanıyoruz . Kelimenin tam anlamıyla bir söz gibi . Ben bir noktada geri dönmek için söz veriyorum jQuery deffered.done deffered.failve deffered.always(diğerleri arasında) denilen belirli geri aramalar kullanır . Hepsini burada görebilirsiniz

Yapmanız gereken şey, kendisine iletilen verilerle bir noktada yürütülmesi vaat edilen bir işlevi geçirmektir.

Geri arama hemen yapılmadığından, ancak daha sonra başvurunun yürütülmediği işleve iletilmesi önemlidir. yani

function foo(bla) {
  console.log(bla)
}

böylece çoğu zaman (ama her zaman değil) size geçmek olacak foodeğilfoo()

Umarım bu bir anlam ifade eder. Böyle kafa karıştırıcı görünen bir şeyle karşılaştığınızda, en azından bunu anlamak için belgeleri tamamen okumanızı tavsiye ederim. Seni çok daha iyi bir geliştirici yapacak.


18

ES2017'yi kullanarak bunu işlev bildirimi olarak almalısınız

async function foo() {
    var response = await $.ajax({url: '...'})
    return response;
}

Ve böyle yürütmek.

(async function() {
    try {
        var result = await foo()
        console.log(result)
    } catch (e) {}
})()

Veya Promise sözdizimi

foo().then(response => {
    console.log(response)

}).catch(error => {
    console.log(error)

})

ikinci işlevi yeniden kullanılabilir olabilir?
Zum Dummi

Oncolse, log çağrılırsa sonuçları nasıl kullanıyorsunuz? Her şey o noktada konsola gitmiyor mu?
Ken Ingram
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.