jQuery ertelendi ve vaat etti - .then () vs .done ()


473

JQuery ertelemeler ve vaatler hakkında okuyorum ve başarılı geri aramalar için .then()& kullanarak arasındaki farkı göremiyorum .done(). Eric Hynds'ın bundan bahsettiğini .done()ve .success()aynı işlevselliğe eşleştirdiğini biliyorum , ancak .then()tüm geri aramaların hepsi başarılı bir işlemin tamamlanması üzerine çağrıldığı için tahmin ediyorum .

Herkes beni doğru kullanıma aydınlatabilir mi?


15
Lütfen Haziran 2016'da piyasaya sürülen JQuery 3.0'ın Promises / A + ve ES2015 Promises spesifikasyonlarına uygun ilk sürüm olduğunu unutmayın. Bundan önceki uygulama, vaatlerin yerine getirmesi gerekenlerle uyumsuzluk gösterdi.
Flimm

Cevabımı ne zaman kullanacağım konusunda geliştirilmiş bir öneri ile güncelledim .
Robert Siemer

Yanıtlar:


577

done()Ertelenen çözümlendiğinde, ekli geri aramalar tetiklenir. fail()Ertelenen reddedildiğinde ekli geri aramalar tetiklenir.

JQuery 1.8'den önce then(), sadece sözdizimsel şekerdi:

promise.then( doneCallback, failCallback )
// was equivalent to
promise.done( doneCallback ).fail( failCallback )

1.8 itibariyle, yeni bir vaat then()için takma addır pipe()ve yeni bir söz verir, daha fazla bilgi için buraya bakın pipe().

success()ve error()yalnızca jqXHRçağrıyla döndürülen nesnede kullanılabilir ajax(). Bunlar sırasıyla done()ve için basit takma adlardır fail():

jqXHR.done === jqXHR.success
jqXHR.fail === jqXHR.error

Ayrıca, done()tek bir geri arama ile sınırlı değildir ve işlevsizliği filtreler (1.8.1'de düzeltilmesi gereken 1.8.1'de dizeleri olan bir hata olsa da):

// this will add fn1 to 7 to the deferred's internal callback list
// (true, 56 and "omg" will be ignored)
promise.done( fn1, fn2, true, [ fn3, [ fn4, 56, fn5 ], "omg", fn6 ], fn7 );

Aynı şey geçerli fail().


8
thenyeni bir söz vermek eksik olduğum önemli bir şeydi. Neden böyle bir zincirin tanımsız $.get(....).done(function(data1) { return $.get(...) }).done(function(data2) { ... })olarak başarısız olduğunu anlayamadım data2; Ben değişince doneiçin thengerçekten boru vaat isteyen araya ziyade orijinal sözünü daha işleyicileri eklemek için olduğunu, çalıştı.
wrschneider

5
jQuery 3.0, Promises / A + ve ES2015 spec ile uyumlu ilk sürümdür.
Flimm

4
Neden birini diğerinin üzerinde kullanacağımı hala anlamıyorum. Bir ajax çağrısı yaparsam ve başka bir ajax çağrısı çağırmadan önce bu çağrı tamamen tamamlanıncaya kadar beklemem gerekiyorsa (yani cevap sunucudan döndürülür), doneya da then? Neden?
CodingYoshi

@CodingYoshi Sonunda bu soruyu cevaplamak için cevabımı kontrol et (kullan .then()).
Robert Siemer

413

Dönüş sonuçları işlenir bu şekilde fark vardır (onun adı zincirleme, donedoes not zincir ise thençağrı zincirleri üretir)

promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return 123;
}).then(function (x){
    console.log(x);
}).then(function (x){
    console.log(x)
})

Aşağıdaki sonuçlar günlüğe kaydedilir:

abc
123
undefined

Süre

promise.done(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return 123;
}).done(function (x){
    console.log(x);
}).done(function (x){
    console.log(x)
})

aşağıdakileri alacak:

abc
abc
abc

---------- Güncelleme:

Btw. Atom tipi değeri yerine bir Promise döndürürseniz, dış promise iç vaat çözülene kadar bekleyecektir:

promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return $http.get('/some/data').then(function (result) {
        console.log(result); // suppose result === "xyz"
        return result;
    });
}).then(function (result){
    console.log(result); // result === xyz
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})

bu şekilde, aşağıdaki gibi paralel veya sıralı asenkron işlemler oluşturmak çok kolay hale gelir:

// Parallel http requests
promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);

    var promise1 = $http.get('/some/data?value=xyz').then(function (result) {
        console.log(result); // suppose result === "xyz"
        return result;
    });

    var promise2 = $http.get('/some/data?value=uvm').then(function (result) {
        console.log(result); // suppose result === "uvm"
        return result;
    });

    return promise1.then(function (result1) {
        return promise2.then(function (result2) {
           return { result1: result1, result2: result2; }
        });
    });
}).then(function (result){
    console.log(result); // result === { result1: 'xyz', result2: 'uvm' }
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})

Yukarıdaki kod paralel olarak iki http isteği yayınlar ve böylece isteklerin daha erken tamamlanmasını sağlarken, bu http isteklerinin altında sırayla çalıştırılır ve böylece sunucu yükü azaltılır

// Sequential http requests
promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);

    return $http.get('/some/data?value=xyz').then(function (result1) {
        console.log(result1); // suppose result1 === "xyz"
        return $http.get('/some/data?value=uvm').then(function (result2) {
            console.log(result2); // suppose result2 === "uvm"
            return { result1: result1, result2: result2; };
        });
    });
}).then(function (result){
    console.log(result); // result === { result1: 'xyz', result2: 'uvm' }
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})

121
Sonucu donehiçbir şey yapmayan, sonucu thendeğiştiren kavram için +1 . Diğerleri imo tarafından kaçırılan büyük bir nokta.
Shanimal

9
Muhtemelen jQuery'nin hangi sürümü için geçerli olduğunu belirtmeye değer, çünkü then1.8'de değişti
bradley.ayers

4
+1 Doğrudan noktaya. Herkes karışık ve çağrıları ile hangi zincirleri sonuç görmek istiyorsa çalıştırılabilir bir örnek oluşturdum .donethen
Michael Kropat

7
Yukarıdaki örnek ayrıca 'tamamlandı' nın başlangıçta oluşturulan orijinal söz nesnesi üzerinde çalıştığını, ancak 'sonra'nın yeni bir söz verdiğini vurgulamaktadır.
Pulak Kanti Bhattacharyya

2
Bu, jQuery 1.8+ için geçerlidir. Eski sürümler de doneörnek gibi davranır . Değişim theniçin pipede önceden-1.8 1.8+ almak thendavranışı.
David Harkness

57

.done() yalnızca bir geri arama vardır ve başarılı geri aramadır

.then() hem başarılı hem de başarısız geri aramalara sahip

.fail() yalnızca bir başarısız geri arama var

bu yüzden ne yapmanız gerekiyor size kalmış ... başarılı mı yoksa başarısız mı?


18
'O zaman'ın çağrı zincirleri ürettiğinden bahsetmezsiniz. Lu4'ün cevabına bakın.
oligofren

Cevabınız 2011'den ... Bugünlerde geri dönüş değerleri then()çok farklı done(). then()Genellikle sadece başarı geri çağırma ile çağrıldığı gibi , nokta hatırlamak / bilmek ana şeyden ziyade bir ayrıntı. (JQuery 3.0'dan önce nasıl olduğunu söyleyemeyiz.)
Robert Siemer

14

deferred.done ()

yalnızca Ertelenmiş çözümlendiğinde çağrılacak işleyicileri ekler . Aranacak birden çok geri arama ekleyebilirsiniz.

var url = 'http://jsonplaceholder.typicode.com/posts/1';
$.ajax(url).done(doneCallback);

function doneCallback(result) {
    console.log('Result 1 ' + result);
}

Yukarıdaki gibi de yazabilirsiniz,

function ajaxCall() {
    var url = 'http://jsonplaceholder.typicode.com/posts/1';
    return $.ajax(url);
}

$.when(ajaxCall()).then(doneCallback, failCallback);

deferred.then ()

Ertelendi çözüldüğünde, reddedildiğinde veya hala devam ederken çağrılacak işleyicileri ekler .

var url = 'http://jsonplaceholder.typicode.com/posts/1';
$.ajax(url).then(doneCallback, failCallback);

function doneCallback(result) {
    console.log('Result ' + result);
}

function failCallback(result) {
    console.log('Result ' + result);
}

gönderiniz, geri arama thenyapılmadığında nasıl davrandığını netleştirmez fail- yani faildavayı hiç yakalamaz
BM

Başarısızlık durumu, programın en üst düzeyi tarafından yakalanabilecek bir istisna oluşturur. İstisnayı JavaScript konsolunda da görebilirsiniz.
David Spector

10

Aslında, jQuery'nin Ertelenmişlerinin bir Vaat uygulaması olması gerektiği (ve jQuery3.0 aslında onları spesifik hale getirmeye çalıştığı sürece) oldukça kritik bir fark var.

Done / then arasındaki en önemli fark

  • .done() HER ZAMAN, ne yaptığınıza veya ne döndürdüğünüze bakılmaksızın başladığı aynı Söz / sarılmış değerleri döndürür.
  • .then() her zaman YENİ bir Promise döndürür ve bu Promise'ın neyi geri döndürdüğünüz fonksiyona bağlı olduğunu kontrol etmekle yükümlüsünüz.

JQuery'den yerel ES2015 Promises'e çevrilir, .done()bir Promise zincirindeki bir fonksiyonun etrafına bir "dokunma" yapısı uygulamak gibi bir şeydir, çünkü zincir "çözülme" durumundaysa, bir fonksiyona bir değer iletir. ancak bu işlevin sonucu zincirin kendisini ETKİLEMEZ.

const doneWrap = fn => x => { fn(x); return x };

Promise.resolve(5)
       .then(doneWrap( x => x + 1))
       .then(doneWrap(console.log.bind(console)));

$.Deferred().resolve(5)
            .done(x => x + 1)
            .done(console.log.bind(console));

Her ikisi de 6 değil, 5'i kaydeder.

Günlük kaydı yapmak için done ve doneWrap kullandığımı unutmayın. Değil. Çünkü console.log işlevleri aslında hiçbir şey döndürmez. Ve eğer bir şey döndürmeyen bir işlevi geçerseniz ne olur?

Promise.resolve(5)
       .then(doneWrap( x => x + 1))
       .then(console.log.bind(console))
       .then(console.log.bind(console));

Günlüğe kaydedilecek:

5

Tanımsız

Ne oldu? Ne zaman .then kullandığım ve hiçbir şey döndürmeyen bir işlevi geçtiğimde, örtük sonuç "tanımsız" ... elbette tanımsız olarak kaydedilen bir sonraki metoda bir Promde [tanımsız] döndürdü. Böylece başladığımız orijinal değer temel olarak kayboldu.

.then()kalpte bir fonksiyon kompozisyonu biçimidir: her adımın sonucu bir sonraki adımda fonksiyonun argümanı olarak kullanılır. Bu yüzden .done en iyi şekilde bir "musluk" olarak düşünülür -> aslında kompozisyonun bir parçası değildir, sadece belirli bir adımdaki değere bir göz atar ve bu değerde bir işlevi çalıştırır, ancak aslında değişmez bileşimi herhangi bir şekilde.

Bu oldukça temel bir farktır ve yerli Vaatlerin kendilerinin bir .done yönteminin uygulanmamasının muhtemelen iyi bir nedeni vardır. Biz neden .fail yöntemi yok içine girmek zorunda değilsiniz, çünkü bu daha karmaşık (yani .fail / .catch .done / .then -> işlevlerinde çıplak değerleri döndüren aynalar DEĞİLDİR "kal" reddedilenler gibi reddedildi. daha sonra, çözüyorlar!)


6

then()her durumda her durumda çağırılacağı anlamına gelir. Ancak, geçen parametreler farklı jQuery sürümlerinde farklıdır.

JQuery 1.8 öncesi then()eşittir done().fail(). Ve tüm geri arama işlevleri aynı parametreleri paylaşır.

Ancak jQuery 1.8'den itibaren then()yeni bir vaat döndürür ve eğer bir değer döndürürse, bir sonraki geri çağırma işlevine geçirilir.

Aşağıdaki örneği görelim:

var defer = jQuery.Deferred();

defer.done(function(a, b){
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
}).then(function( a, b ) {
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
}).then(function( a, b ) {
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
});

defer.resolve( 3, 4 );

JQuery 1.8'den önce cevap şu olmalıdır:

result = 3
result = 3
result = 3

Hepsi 3 resultalır. Ve then()işlev her zaman aynı ertelenmiş nesneyi bir sonraki işleve geçirir.

Ancak jQuery 1.8 itibariyle, sonuç şöyle olmalıdır:

result = 3
result = 7
result = NaN

Çünkü ilk then()işlev yeni bir söz verir ve 7 değeri (ve bu iletilecek tek parametredir) bir sonrakine geçer done(), böylece ikinci done()yazma result = 7. İkincisi then()7 değerini aalır undefinedve değeri olarak alır b, böylece ikincisi then()NaN parametresi ile yeni bir vaat döndürür ve son done()olarak sonuç olarak NaN yazdırır.


"then () her zaman her durumda çağırılacağı anlamına gelir" - doğru değil. Promise içinde bir hata olması durumunda asla () çağrılmaz.
David Spector

A'nın jQuery.Deferred()birincisine düzgün bir şekilde ilettiği birden çok değer alabileceği ilginç bir özellik — .then()Biraz garip ... ... aşağıdaki .then()gibi bunu yapamaz. (Seçilen arabirim returnyalnızca bir değer döndürebilir.) Javascript'in doğal özelliği Promisebunu yapmaz. (Bu daha tutarlı, dürüst olmak gerekirse.)
Robert Siemer


2

Sadece kullan .then()

Bunlar dezavantajları .done()

  • zincirleme yapılamaz
  • resolve()çağrıyı engelle (tüm .done()işleyiciler senkronize olarak yürütülür)
  • resolve()kayıtlı .done()işleyicilerin istisnası olabilir (!)
  • .done()ertelenmiş bir istisna ertelenir:
    • diğer .done()işleyiciler sessizce atlanacak

Geçici olarak .then(oneArgOnly)her zaman gerektiren bir .catch()istisna yok sayılır, ancak bu artık doğru değil düşündüm : unhandledrejectionolay .then()konsolda işlenmeyen istisnaları (varsayılan olarak) günlüğe kaydeder . Çok makul! Kullanmak için hiçbir neden kalmadı .done().

Kanıt

Aşağıdaki kod pasajı şunu göstermektedir:

  • tüm .done()işleyiciler şu noktada senkronize olarak adlandırılacaktırresolve()
    • 1, 3, 5, 7 olarak kaydedildi
    • komut dosyası alttan düşmeden günlüğe kaydedilir
  • bir .done()etki resolve()arayan kişiye istisna
    • yakalamak yoluyla günlüğe kaydedildi resolve()
  • İstisna sonları daha da dan söz .done()çözünürlüğü
    • 8 ve 10 günlüğe kaydedilmedi!
  • .then() bu problemlerin hiçbirine sahip değil
    • iş parçacığı boşta döndükten sonra 2, 4, 6, 9, 11 olarak kaydedildi
    • (snippet ortamında hiçbir unhandledrejectionşey görünmüyor)

Btw, istisnalar .done()düzgün bir şekilde yakalanamıyor: eşzamanlı modeli nedeniyle .done(), hata ya .resolve()( .done()erteleme zaten çözülmüşse suçluyu ekleyen çağrı noktasına) (ya kütüphane kodu olabilir!)

console.log('Start of script.');
let deferred = $.Deferred();
// deferred.resolve('Redemption.');
deferred.fail(() => console.log('fail()'));
deferred.catch(()=> console.log('catch()'));
deferred.done(() => console.log('1-done()'));
deferred.then(() => console.log('2-then()'));
deferred.done(() => console.log('3-done()'));
deferred.then(() =>{console.log('4-then()-throw');
    throw 'thrown from 4-then()';});
deferred.done(() => console.log('5-done()'));
deferred.then(() => console.log('6-then()'));
deferred.done(() =>{console.log('7-done()-throw');
    throw 'thrown from 7-done()';});
deferred.done(() => console.log('8-done()'));
deferred.then(() => console.log('9-then()'));

console.log('Resolving.');
try {
    deferred.resolve('Solution.');
} catch(e) {
    console.log(`Caught exception from handler
        in resolve():`, e);
}
deferred.done(() => console.log('10-done()'));
deferred.then(() => console.log('11-then()'));
console.log('End of script.');
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha384-vk5WoKIaW/vJyUAd9n/wmopsmNhiy+L2Z+SBxGYnUkunIxVxAv/UtMOhba/xskxh"
crossorigin="anonymous"
></script>


Birkaç şey: 1) Daha doneönce yapılan bir istisna varsa, bunun yürütülmeyeceğini söylediğini görüyorum . Ama neden sessizce görmezden gelinecekti, yani bir istisna oluştu, neden sessiz olduğunu söylüyorsun? 2)Deferred API'sı çok çok kötü yapıldığı için nesneyi hor görüyorum . Çok karmaşık ve kafa karıştırıcı. Buradaki kodunuz ya noktanızı kanıtlamanıza yardımcı olmaz ve kanıtlamaya çalıştığınız şey için çok fazla gereksiz karmaşıklığa sahiptir. 3)done 2, 4 ve 6 numaralı indeks neden 2.'den önce yapılıyor then?
KodlamaYoshi

Benim hatam, kesinlikle bir oyu hak ediyorsun. İstisna hakkındaki yorumunuza gelince, normalde istisnalar böyle çalışır: bir kez yükseltildikten sonra yürütülmeyecek kod. Ayrıca jQuery belgeleri, yalnızca ertelenen çözümlendiğinde yürütüleceğini belirtir.
CodingYoshi

@CodingYoshi Burada durum farklı: Sadece çözülmüş vaatler / ertelemelerden bahsediyordum. Başarı işleyicinin geri kalanının çağrılmadığından şikayet etmiyorum, bu normal. Ancak başarılı bir vaatte tamamen farklı bir başarı işleyicisinin çağrılmasının bir nedeni göremiyorum. Tümü .then()çağrılacak, istisna (bu işleyicilerde) yükseltilecek veya yükseltilmeyecektir. Ancak toplama / kalan .done()mola.
Robert Siemer

@CodingYoshi Söylememe izin verilirse cevabımı büyük ölçüde geliştirdim. Kod ve metin.
Robert Siemer

1

JQuery 3.0'dan itibaren beklenmedik davranışlara kolayca yol açabilecek ve önceki cevaplarda belirtilmeyen bir hayati fark daha var :

Aşağıdaki kodu göz önünde bulundurun:

let d = $.Deferred();
d.done(() => console.log('then'));
d.resolve();
console.log('now');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>

bu çıktı:

then
now

Şimdi, yerine done()göre then()aynı snippet'te:

var d = $.Deferred();
d.then(() => console.log('then'));
d.resolve();
console.log('now');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>

çıktı şimdi:

now
then

Dolayısıyla, derhal çözümlenen ertelemeler için, iletilen işlev done()her zaman eşzamanlı olarak çağrılırken, iletilen herhangi bir bağımsız değişken zaman then()uyumsuz olarak çağrılır.

Bu, yükseltme kılavuzunda belirtildiği gibi her iki geri çağrının senkronize olarak çağrıldığı önceki jQuery sürümlerinden farklıdır :

Promises / A + uyumluluğu için gerekli olan diğer bir davranış değişikliği, Ertelenmiş .then () geri çağrılarının her zaman eşzamansız olarak çağrılmasıdır. Önceden, zaten çözülmüş veya reddedilmiş bir Ertelenmiş'e bir .then () geri çağrısı eklenmişse, geri çağrı hemen ve eşzamanlı olarak çalışırdı.


-5

.done()başka hiçbir şeyin başka adımlar ekleyemeyeceğinden emin olarak söz zincirini sonlandırır. Bu, jQuery söz uygulamasının işlenmeyen bir istisnayı atabileceği anlamına gelir, çünkü kimse bunu kullanarak başaramaz .fail().

Pratik anlamda, bir söze daha fazla adım atmayı planlamıyorsanız, kullanmalısınız .done(). Daha fazla ayrıntı için vaatlerin neden yapılması gerektiğini görün


6
Dikkat! Bu cevap, birkaç vaat uygulaması için doğru olacaktır, ancak .done()sonlandırıcı bir rolü olmayan jQuery için geçerli değildir. Dokümantasyonda "deferred.done (), ertelenen nesneyi döndürdüğü için, ertelenmiş nesnenin diğer yöntemleri, buna ek .done () yöntemleri dahil olmak üzere zincirlenebilir" der. .fail()bahsedilmez ama evet, bu da zincirlenebilir.
Roamer-1888

1
Benim kötü, jQuery kontrol etmedi
gleb bahmutov

1
@glebbahmutov - belki başkalarının kafasını karıştırmamak için bu cevabı silmelisin? Sadece dostça bir öneri :)
Andrey

2
Lütfen cevabı silmeyin, bu insanların yanlış anlamalarını da gidermelerine yardımcı olabilir.
Melissa
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.