jQuery odracza i obiecuje - .then () vs .done ()

473

Czytałem o odroczeniu i obietnicach jQuery i nie widzę różnicy między korzystaniem z .then()& .done()dla udanych połączeń zwrotnych. Wiem, że Eric Hynds wspomina o tym .done()i .success()odwzorowuje tę samą funkcjonalność, ale chyba tak, .then()ponieważ wszystkie wywołania zwrotne są wywoływane po zakończeniu udanej operacji.

Czy ktoś może oświecić mnie do prawidłowego użytkowania?

screenm0nkey
źródło
15
Należy pamiętać, że JQuery 3.0 wydany w czerwcu 2016 r. Był pierwszą wersją zgodną ze specyfikacjami Promises / A + i ES2015 Promises. Wcześniejsze wdrożenie było niezgodne z tym, co miały przynieść obietnice.
Flimm,
Zaktualizowałem swoją odpowiedź ulepszoną rekomendacją dotyczącą tego, kiedy użyć.
Robert Siemer

Odpowiedzi:

577

Załączone wywołania zwrotne done()zostaną uruchomione, gdy odroczone zostanie rozwiązane. Załączone wywołania zwrotne fail()zostaną uruchomione, gdy odroczone zostanie odrzucone.

Przed wersją jQuery 1.8 then()był tylko cukier syntaktyczny:

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

Od wersji 1.8 then()jest pseudonimem pipe()i zwraca nową obietnicę, więcej informacji znajdziesz tutajpipe() .

success()i error()są dostępne tylko dla jqXHRobiektu zwróconego przez wywołanie do ajax(). Są to proste aliasy dla done()i fail()odpowiednio:

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

Ponadto done()nie ogranicza się do pojedynczego wywołania zwrotnego i odfiltrowuje niefunkcje (chociaż w wersji 1.8 występuje błąd dotyczący ciągów znaków, który należy naprawić w wersji 1.8.1):

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

To samo dotyczy fail().

Julian Aubourg
źródło
8
thenzwrócenie nowej obietnicy było kluczową rzeczą, której mi brakowało. Nie mogłem zrozumieć, dlaczego łańcuch taki $.get(....).done(function(data1) { return $.get(...) }).done(function(data2) { ... })zawodzi z data2nieokreślonym; kiedy zmieniło donesię thento działało, bo naprawdę chcą obietnic rurowych razem zamiast dołączać kolejne ładowarki do oryginalnego obietnicy.
wrschneider
5
jQuery 3.0 to pierwsza wersja zgodna ze specyfikacjami Promises / A + i ES2015.
Flimm,
4
Nadal nie rozumiem, dlaczego miałbym używać jednego nad drugim. Jeśli wykonam wywołanie ajax i muszę poczekać, aż to połączenie zostanie w pełni zakończone (co oznacza, że ​​odpowiedź jest zwracana z serwera), zanim wywołam kolejne wywołanie ajax, czy używam donelub then? Dlaczego?
Kodowanie Yoshi
@CodingYoshi Sprawdź moją odpowiedź, aby w końcu odpowiedzieć na to pytanie (użyj .then()).
Robert Siemer
413

Istnieje również różnica w sposobie przetwarzania wyników zwracanych (nazywa się to łańcuchem, donenie łączy się podczas thentworzenia łańcuchów połączeń)

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

Zostaną zarejestrowane następujące wyniki:

abc
123
undefined

Podczas

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

otrzyma:

abc
abc
abc

---------- Aktualizacja:

Btw. Zapomniałem wspomnieć, że jeśli zwrócisz obietnicę zamiast wartości typu atomowego, obietnica zewnętrzna będzie czekać, aż obietnica wewnętrzna zostanie rozwiązana:

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

w ten sposób bardzo proste staje się komponowanie równoległych lub sekwencyjnych operacji asynchronicznych, takich jak:

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

Powyższy kod wysyła dwa żądania HTTP równolegle, dzięki czemu żądania są ukończone wcześniej, podczas gdy poniżej te żądania są uruchamiane sekwencyjnie, co zmniejsza obciążenie serwera

// 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
})
Lu4
źródło
121
+1 za pojęcie, które donenie ma wpływu na wynik, gdy thenzmienia wynik. Ogromny punkt pominięty przez innych imo.
Shanimal
9
Prawdopodobnie warto wspomnieć, do której wersji jQuery to dotyczy, ponieważ zachowanie thenzmieniło się w wersji 1.8
bradley.ayers
4
+1 prosto do celu. Stworzyłem możliwy do uruchomienia przykład, jeśli ktoś chce zobaczyć, co skutkuje połączeniami mieszanymi donei thenwywołaniami.
Michael Kropat
7
powyższy przykład podkreśla również, że „gotowe” działa na pierwotnie utworzonym obiekcie przyrzeczenia, ale „potem” zwraca nową obietnicę.
Pulak Kanti Bhattacharyya
2
Dotyczy to jQuery 1.8+. Starsze wersje działają tak jak w doneprzykładzie. Zmień thenna pipewcześniejszą niż 1.8, aby uzyskać thenzachowanie 1.8+ .
David Harkness
57

.done() ma tylko jedno połączenie zwrotne i jest to połączenie zwrotne zakończone powodzeniem

.then() ma zarówno callback powodzenia, jak i niepowodzenia

.fail() ma tylko jedno niepowodzenie wywołania zwrotnego

więc to od Ciebie zależy, co musisz zrobić ... czy obchodzi Cię, czy się powiedzie, czy nie?

Marino Šimić
źródło
18
Nie wspominasz, że „następnie” tworzy łańcuchy połączeń. Zobacz odpowiedź Lu4.
oligofren
Twoja odpowiedź pochodzi z 2011 roku ... W dzisiejszych czasach ich wartości zwrotne then()bardzo się różnią done(). Jak then()często nazywane jest tylko zwrotem sukcesu, twój punkt jest raczej szczegółem niż najważniejszą rzeczą do zapamiętania / poznania. (Nie można powiedzieć, jak było przed jQuery 3.0.)
Robert Siemer
14

deferred.done ()

dodaje procedury obsługi, które mają być wywoływane tylko po rozstrzygnięciu opcji Odroczony . Możesz dodać wiele wywołań zwrotnych, które będą wywoływane.

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

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

Możesz także napisać powyżej w ten sposób,

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

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

deferred.then ()

dodaje procedury obsługi, które mają być wywoływane, gdy funkcja odroczenia jest rozpatrywana, odrzucana lub wciąż trwa .

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);
}
Nipuna
źródło
Twój post nie wyjaśnia, jak się thenzachowuje, jeśli nie failzostanie udzielone oddzwonienie - a mianowicie nie uchwycenie failsprawy
BM
Przypadek niepowodzenia generuje wyjątek, który może zostać przechwycony przez najwyższy poziom programu. Możesz także zobaczyć wyjątek w konsoli JavaScript.
David Spector,
10

Jest tak naprawdę dość istotna różnica, o ile Odroczone jQuery mają być implementacjami Obietnic (a jQuery3.0 faktycznie próbuje wprowadzić je w specyfikację).

Kluczowa różnica między wykonaniem / następnie jest taka

  • .done() ZAWSZE zwraca te same obietnice / zapakowane wartości, od których zaczął, niezależnie od tego, co robisz lub co zwracasz.
  • .then() zawsze zwraca NOWĄ obietnicę, a ty jesteś odpowiedzialny za kontrolowanie, co ta obietnica jest oparta na funkcji, którą przekazałeś.

Przetłumaczone z jQuery na natywne obietnice ES2015, .done()jest coś w rodzaju implementacji struktury „dotknij” wokół funkcji w łańcuchu obietnicy, w tym sensie, że jeśli łańcuch jest w stanie „rozstrzygnięcia”, przekaże wartość do funkcji .. , ale wynik tej funkcji NIE wpłynie na sam łańcuch.

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

Oba będą rejestrować 5, a nie 6.

Zauważ, że użyłem done i doneWrap do rejestrowania, a nie. Następnie. To dlatego, że funkcje console.log nie zwracają niczego. A co się stanie, jeśli zdasz. Następnie funkcję, która nic nie zwraca?

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

To będzie rejestrować:

5

nieokreślony

Co się stało? Kiedy użyłem .the i przekazałem mu funkcję, która niczego nie zwróciła, jej domyślnym rezultatem było „niezdefiniowane” ... co oczywiście zwróciło Obietnicę [niezdefiniowana] do następnej metody, która zalogowała się niezdefiniowana. Tak więc pierwotna wartość, od której zaczęliśmy, została zasadniczo utracona.

.then()jest w istocie formą kompozycji funkcji: wynik każdego kroku jest wykorzystywany jako argument funkcji w następnym kroku. Właśnie dlatego .done najlepiej jest traktować jako „stuknięcie” -> to nie jest tak naprawdę część kompozycji, tylko coś, co rzuca okiem na wartość na pewnym etapie i uruchamia funkcję przy tej wartości, ale tak naprawdę nie zmienia kompozycja w jakikolwiek sposób.

Jest to dość podstawowa różnica i prawdopodobnie istnieje dobry powód, dla którego natywne obietnice nie mają zaimplementowanej metody .done. Nie musimy się zastanawiać, dlaczego nie ma metody .fail, ponieważ jest to nawet bardziej skomplikowane (mianowicie .fail / .catch NIE są dublowaniem funkcji .done / .the -> funkcje w .catch, które zwracają same wartości, nie „zostań” odrzucony tak, jak te przekazane do. następnie rozwiązują!)

Dtipson
źródło
6

then()zawsze oznacza, że ​​zostanie wywołany w każdym przypadku. Ale przekazywane parametry są różne w różnych wersjach jQuery.

Przed jQuery 1.8 then()równa się done().fail(). Wszystkie funkcje zwrotne mają te same parametry.

Ale od jQuery 1.8 then()zwraca nową obietnicę, a jeśli zwróci wartość, zostanie przekazana do następnej funkcji wywołania zwrotnego.

Zobaczmy następujący przykład:

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

Przed wersją jQuery 1.8 odpowiedź powinna brzmieć

result = 3
result = 3
result = 3

Wszystko resultzajmuje 3. I then()funkcja zawsze przekazuje ten sam odroczony obiekt do następnej funkcji.

Ale od jQuery 1.8 wynik powinien być:

result = 3
result = 7
result = NaN

Ponieważ pierwsza then()funkcja zwraca nową obietnicę, a wartość 7 (i jest to jedyny parametr, który zostanie przekazany) jest przekazywana do następnej done(), więc drugi done()zapis result = 7. Drugi then()przyjmuje 7 jako wartość ai przyjmuje undefinedjako wartość b, więc drugi then()zwraca nową obietnicę z parametrem NaN, a ostatni done()wypisuje NaN jako wynik.

JasmineOT
źródło
„następnie () zawsze oznacza, że ​​zostanie wywołane w każdym przypadku” - nieprawda. następnie () nigdy nie jest wywoływany w przypadku błędu w obietnicy.
David Spector
Interesujący aspekt, w którym a jQuery.Deferred()może otrzymać wiele wartości, które właściwie przekazuje do pierwszej .then(). - Trochę to dziwne ... ponieważ żaden inny .then()nie może tego zrobić. (Wybrany interfejs przez returnmoże zwrócić tylko jedną wartość.) Język rodzimy Javascript Promisetego nie robi. (Szczerze mówiąc, co jest bardziej konsekwentne)
Robert Siemer
3

Istnieje bardzo proste mapowanie myśli w odpowiedzi, które było nieco trudne do znalezienia w innych odpowiedziach:

BM
źródło
2

Jedynym zastosowaniem .then()

Są to wady .done()

  • nie może być przykuty
  • blokada resolve()wywołania (wszystkie .done()procedury obsługi będą wykonywane synchronicznie)
  • resolve()może uzyskać wyjątek od zarejestrowanych .done()programów obsługi (!)
  • wyjątek w .done()połowie zabija odroczonych:
    • Dalsze .done()Wozy będą dyskretnie pominięte

Myślałem tymczasowo, że to .then(oneArgOnly)zawsze wymaga .catch(), aby żaden wyjątek nie był dyskretnie ignorowany, ale nie jest to już prawdą: unhandledrejectiondzienniki zdarzeń rejestrują nieobsługiwane .then()wyjątki w konsoli (domyślnie). Bardzo rozsądne! .done()W ogóle nie ma powodu, by z niego korzystać .

Dowód

Poniższy fragment kodu ujawnia, że:

  • wszystkie .done()programy obsługi będą nazywane synchronicznymi w punkcieresolve()
    • zalogowany jako 1, 3, 5, 7
    • zalogowany, zanim skrypt spadnie do dołu
  • wyjątek .done()wpływa na resolve()rozmówcę
    • zalogowany przez catch dookoła resolve()
  • wyjątek łamie obietnicę dalszego .done()rozwiązania
    • 8 i 10 nie są rejestrowane!
  • .then() nie ma żadnego z tych problemów
    • zalogowany jako 2, 4, 6, 9, 11 po tym, jak nić stanie się bezczynna
    • ( unhandledrejectionwydaje się, że środowisko fragmentu nie ma jest)

Btw, wyjątki od .done()nie mogą być właściwie wychwycone: z powodu synchronicznego wzorca .done()błąd jest zgłaszany w punkcie .resolve()(może to być kod biblioteki!) Lub w .done()wywołaniu, które dołącza sprawcę, jeśli odroczone jest już rozwiązane.

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>

Robert Siemer
źródło
Kilka rzeczy: 1) Widzę, co mówisz, że donenie zostanie wykonane, jeśli poprzednia czynność ma wyjątek. Ale dlaczego miałby być cicho ignorowany, mam na myśli wyjątek, więc dlaczego mówisz, że jest cicho? 2) Nienawidzę Deferredobiektu, ponieważ jego interfejs API jest bardzo, bardzo źle wykonany. Jest to zbyt skomplikowane i mylące. Twój kod tutaj nie pomaga również udowodnić twojego punktu i ma zbyt wiele niepotrzebnej złożoności dla tego, co próbujesz udowodnić. 3) Dlaczego doneindeksy 2, 4 i 6 są wykonywane przed drugim then?
Kodowanie Yoshi
Mój zły, zdecydowanie zasługujesz na głos. Jeśli chodzi o komentarz na temat wyjątku, zwykle tak działają wyjątki: po zgłoszeniu kod po nim nie zostanie wykonany. Dodatkowo dokumentacja jQuery stwierdza, że ​​zostanie wykonana tylko wtedy, gdy odroczone zostanie rozwiązane.
Kodowanie Yoshi
@CodingYoshi Sytuacja wygląda inaczej: mówiłem tylko o rozwiązanych obietnicach / odroczeniach. Nie narzekam, że reszta osoby zajmującej się sukcesem nie jest wywoływana, to normalne. Ale nie widzę powodu, dla którego zupełnie inna osoba zajmująca się sukcesem w sprawie obiecanej obietnicy nie jest nazywana. Wszystkie .then()zostaną wywołane, wyjątek (w tych modułach obsługi) podniesiony lub nie. Ale dodanie / pozostała .done()przerwa.
Robert Siemer
@CodingYoshi Bardzo poprawiłem swoją odpowiedź, jeśli wolno mi to powiedzieć. Kod i tekst.
Robert Siemer
1

Jest jeszcze jedna istotna różnica w stosunku do jQuery 3.0, która może łatwo prowadzić do nieoczekiwanego zachowania i nie jest wspomniana w poprzednich odpowiedziach:

Rozważ następujący kod:

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>

spowoduje to:

then
now

Teraz zamień done()na then()w tym samym fragmencie:

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>

wyjście jest teraz:

now
then

Tak więc, dla natychmiastowo rozwiązanych odroczeń, przekazana funkcja done()zawsze będzie wywoływana synchronicznie, podczas gdy każdy przekazany argument then()jest wywoływany asynchronicznie.

Różni się to od wcześniejszych wersji jQuery, w których oba połączenia zwrotne są wywoływane synchronicznie, jak wspomniano w przewodniku aktualizacji :

Kolejną zmianą zachowania wymaganą dla zgodności Promises / A + jest to, że wywołania odroczone .then () są zawsze wywoływane asynchronicznie. Poprzednio, jeśli wywołanie zwrotne .then () zostało dodane do Odroczonego, które zostało już rozwiązane lub odrzucone, wywołanie zwrotne działałoby natychmiast i synchronicznie.

schellmax
źródło
-5

.done()kończy łańcuch obietnic, upewniając się, że nic więcej nie może załączyć dalszych kroków. Oznacza to, że implementacja obietnicy jQuery może zgłosić dowolny nieobsługiwany wyjątek, ponieważ nikt nie może tego obsłużyć .fail().

W praktyce, jeśli nie planujesz dołączać kolejnych kroków do obietnicy, powinieneś skorzystać .done(). Aby uzyskać więcej informacji, zobacz, dlaczego należy składać obietnice

Gleb Bahmutov
źródło
6
Uwaga! Ta odpowiedź byłaby poprawna dla kilku implementacji obietnic, ale nie dla jQuery, w której .done()nie pełni roli kończącej. Dokumentacja mówi: „Ponieważ deferred.done () zwraca odroczony obiekt, inne metody odroczonego obiektu można powiązać z tym, w tym dodatkowe metody .done ()”. .fail()nie jest wspomniane, ale tak, to też można powiązać.
Roamer-1888,
1
Mój zły, nie sprawdziłem jQuery
gleb bahmutov
1
@glebbahmutov - może powinieneś usunąć tę odpowiedź, aby inni się nie mylili? Po prostu przyjazna sugestia :)
Andrey
2
Nie usuwaj odpowiedzi, może to również pomóc w wyjaśnieniu nieporozumień.
Melissa