jQuery differisce e promette - .then () vs .done ()


473

Ho letto dei rinvii e delle promesse di jQuery e non riesco a vedere la differenza tra usare .then()e .done()per richiamate riuscite. So che Eric Hynds lo menziona .done()e si associa.success() alla stessa funzionalità, ma immagino che lo sia anche .then()perché tutti i callback sono tutti richiamati al completamento di un'operazione riuscita.

Qualcuno può illuminarmi per il corretto utilizzo?


15
Notare a tutti che JQuery 3.0 rilasciato a giugno 2016 è stata la prima versione conforme alle specifiche Promises / A + ed ES2015 Promises. L'implementazione precedente aveva incompatibilità con ciò che le promesse avrebbero dovuto mantenere.
Flimm,

Ho aggiornato la mia risposta con una raccomandazione migliorata su cosa usare quando.
Robert Siemer,

Risposte:


577

I callback collegati done()verranno attivati ​​quando il differito viene risolto. I callback allegati fail()verranno attivati ​​quando il differito viene rifiutato.

Prima di jQuery 1.8, then()era solo zucchero sintattico:

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

A partire da 1.8, then()è un alias per pipe()e restituisce una nuova promessa, vedere qui per ulteriori informazioni su pipe().

success()e error()sono disponibili solo jqXHRsull'oggetto restituito da una chiamata a ajax(). Sono semplici alias per done()e fail()rispettivamente:

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

Inoltre, done()non si limita a un singolo callback e filtrerà le non funzioni (anche se esiste un bug con stringhe nella versione 1.8 che dovrebbe essere risolto in 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 );

Lo stesso vale per fail().


8
thenrestituire una nuova promessa era una cosa fondamentale che mi mancava. Non riuscivo a capire perché una catena come $.get(....).done(function(data1) { return $.get(...) }).done(function(data2) { ... })stava fallendo con data2indefinito; quando ho cambiato donea thenha funzionato, perché ero davvero voglia di promesse tubi insieme piuttosto che attribuisco più gestori alla promessa originale.
wrschneider,

5
jQuery 3.0 è la prima versione conforme alle specifiche Promises / A + ed ES2015.
Flimm,

4
Ancora non capisco perché dovrei usarne uno sopra l'altro. Se eseguo una chiamata Ajax e devo attendere fino al completamento della chiamata (ovvero la risposta viene restituita dal server) prima di chiamare un'altra chiamata Ajax, utilizzo doneo then? Perché?
Codifica Yoshi il

@CodingYoshi Dai un'occhiata alla mia risposta per rispondere finalmente a quella domanda (usa .then()).
Robert Siemer,

413

C'è anche una differenza nel modo in cui i risultati di ritorno vengono elaborati (il suo chiamato concatenamento, donenon si incatena mentre thenproduce catene di chiamate)

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

Verranno registrati i seguenti risultati:

abc
123
undefined

Mentre

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

otterrà il seguente:

abc
abc
abc

---------- Aggiornare:

Btw. Ho dimenticato di menzionare, se restituisci una promessa invece del valore di tipo atomico, la promessa esterna aspetterà che la promessa interna si risolva:

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

in questo modo diventa molto semplice comporre operazioni asincrone parallele o sequenziali come:

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

Il codice sopra emette due richieste http in parallelo, rendendo così le richieste complete prima, mentre al di sotto di tali richieste HTTP vengono eseguite in sequenza riducendo il carico del server

// 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
+1 per la nozione che donenon fa nulla al risultato in cui thencambia il risultato. Enorme punto mancato dagli altri imo.
Shanimal,

9
Vale probabilmente la pena ricordare a quale versione di jQuery si applica, dal momento che il comportamento è thencambiato in 1.8
bradley.ayers,

4
+1 Dritto al punto. Ho creato un esempio eseguibile se qualcuno vuole vedere quali catene con risultati misti donee thenchiamate.
Michael Kropat

7
l'esempio sopra evidenzia anche che 'fatto' funziona sull'oggetto promessa originale creato inizialmente ma 'poi' restituisce una nuova promessa.
Pulak Kanti Bhattacharyya,

2
Questo vale per jQuery 1.8+. Le versioni precedenti si comportano esattamente come donenell'esempio. Passare thena pipepre-1.8 per ottenere il thencomportamento 1.8+ .
David Harkness,

57

.done() ha un solo callback ed è il callback di successo

.then() ha sia callback riusciti che falliti

.fail() ha un solo callback fallito

quindi spetta a te cosa devi fare ... ti importa se ha successo o se fallisce?


18
Non si menziona il fatto che "then" produce catene di chiamate. Vedi la risposta di Lu4.
oligofren,

La tua risposta è del 2011 ... Oggi i loro valori di ritorno sono then()molto diversi da done(). Come then()spesso viene chiamato solo con il callback di successo, il tuo punto è piuttosto un dettaglio piuttosto che la cosa principale da ricordare / conoscere. (Non posso dire com'era prima di jQuery 3.0.)
Robert Siemer,

14

deferred.done ()

aggiunge gestori da chiamare solo quando è stato risolto il rinvio . È possibile aggiungere più callback da chiamare.

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

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

Puoi anche scrivere sopra in questo modo,

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

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

deferred.then ()

aggiunge gestori da chiamare quando Deferred viene risolto, rifiutato o ancora in corso .

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

il tuo post non chiarisce come thensi comporta se non failviene fornita alcuna richiamata - vale a dire che non si cattura affatto il failcaso
BM

Il caso di errore genera un'eccezione che può essere rilevata dal livello superiore del programma. Puoi anche vedere l'eccezione nella console JavaScript.
David Spector,

10

In realtà c'è una differenza piuttosto critica, nella misura in cui i Deferreds di jQuery sono pensati per essere implementazioni di Promises (e jQuery3.0 cerca effettivamente di portarli nelle specifiche).

La differenza chiave tra done / then è quella

  • .done() Restituisce SEMPRE gli stessi valori di Promessa / a capo con cui è iniziato, indipendentemente da ciò che fai o da ciò che restituisci.
  • .then() restituisce sempre una NUOVA Promessa e sei responsabile del controllo di ciò che tale Promessa si basa su ciò che la funzione che hai passato lo ha restituito.

Tradotto da jQuery in promesse ES2015 native, .done()è un po 'come implementare una struttura "tap" attorno a una funzione in una catena Promise, in quanto, se la catena è nello stato "risoluzione", passerà un valore a una funzione. ma il risultato di quella funzione NON influenzerà la catena stessa.

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

Entrambi registreranno 5, non 6.

Si noti che ho usato done e doneWrap per eseguire la registrazione, non .then. Questo perché le funzioni console.log in realtà non restituiscono nulla. E cosa succede se si passa a una funzione che non restituisce nulla?

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

Questo registrerà:

5

non definito

Quello che è successo? Quando ho usato .then e gli ho passato una funzione che non ha restituito nulla, il risultato implicito è stato "indefinito" ... che ovviamente ha restituito una Promessa [indefinita] al successivo metodo, che ha registrato indefinito. Quindi il valore originale con cui abbiamo iniziato era sostanzialmente perso.

.then()è, in sostanza, una forma di composizione della funzione: il risultato di ogni passaggio viene utilizzato come argomento per la funzione nel passaggio successivo. Ecco perché .done è meglio pensato come un "tap" -> in realtà non fa parte della composizione, solo qualcosa che si intrufola nel dare un'occhiata al valore in un determinato passaggio ed esegue una funzione a quel valore, ma in realtà non cambia la composizione in alcun modo.

Questa è una differenza abbastanza fondamentale, e c'è probabilmente una buona ragione per cui Promise native non hanno implementato un metodo .done. Non dobbiamo mai capire perché non esiste un metodo .fail, perché è ancora più complicato (vale a dire .fail / .catch NON sono i mirror di .done / .then -> funzioni in .catch che restituiscono valori nudi non lo fanno "resta" respinto come quelli passati a .quindi, si risolvono!)


6

then()significa sempre che verrà chiamato in ogni caso. Ma i parametri che passano sono diversi nelle diverse versioni di jQuery.

Prima di jQuery 1.8, è then()uguale done().fail(). E tutte le funzioni di callback condividono gli stessi parametri.

Ma a partire da jQuery 1.8, then()restituisce una nuova promessa e, se ha restituito un valore, verrà passato alla successiva funzione di callback.

Vediamo il seguente esempio:

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

Prima di jQuery 1.8, la risposta dovrebbe essere

result = 3
result = 3
result = 3

Tutto resultprende 3. E la then()funzione passa sempre lo stesso oggetto differito alla funzione successiva.

Ma a partire da jQuery 1.8, il risultato dovrebbe essere:

result = 3
result = 7
result = NaN

Perché la prima then()funzione restituisce una nuova promessa e il valore 7 (e questo è l'unico parametro che verrà passato) viene passato alla successiva done(), quindi la seconda done()scrittura result = 7. Il secondo then()prende 7 come valore di ae prende undefinedcome valore di b, quindi il secondo then()restituisce una nuova promessa con il parametro NaN e l'ultimo done()stampa NaN come risultato.


"then () significa sempre che verrà chiamato in ogni caso" - non vero. then () non viene mai chiamato in caso di errore all'interno della Promessa.
David Spector,

Aspetto interessante che a jQuery.Deferred()può ricevere più valori, che passa correttamente al primo .then(). - Un po 'strano però ... poiché nessun seguito .then()non può farlo. (L'interfaccia scelta tramite returnpuò restituire solo un valore.) Il nativo di Javascript Promisenon lo fa. (Il che è più coerente, a dire il vero.)
Robert Siemer

3

C'è una mappatura mentale molto semplice in risposta che era un po 'difficile da trovare nelle altre risposte:


2

Usa solo .then()

Questi sono gli svantaggi di .done()

  • non può essere incatenato
  • blocco della resolve()chiamata (tutti i .done()gestori verranno eseguiti in modo sincrono)
  • resolve()potrebbe ottenere un'eccezione dai .done()gestori registrati (!)
  • un'eccezione in .done()mezzo uccide il differito:
    • ulteriori .done()gestori verranno silenziosamente ignorati

Ho pensato temporaneamente che .then(oneArgOnly)richiede sempre .catch()che nessuna eccezione venga silenziosamente ignorata, ma ciò non è più vero: l' unhandledrejectionevento registra le .then()eccezioni non gestite sulla console (come impostazione predefinita). Molto ragionevole! Nessun motivo rimasto da usare .done()affatto.

Prova

Il seguente frammento di codice rivela che:

  • tutti i .done()gestori saranno chiamati sincroni al punto diresolve()
    • registrato come 1, 3, 5, 7
    • registrato prima che lo script cada in fondo
  • eccezione in un chiamante di .done()influenzeresolve()
    • registrato tramite catch around resolve()
  • l'eccezione rompe promessa da ulteriore .done()risoluzione
    • 8 e 10 non sono registrati!
  • .then() non ha nessuno di questi problemi
    • registrato come 2, 4, 6, 9, 11 dopo che il thread diventa inattivo
    • (l'ambiente snippet non ha unhandledrejectionis is sembra)

A proposito, le eccezioni da .done()non possono essere colte correttamente: a causa del modello sincrono di .done(), l'errore viene lanciato al punto .resolve()(potrebbe essere il codice della libreria!) O alla .done()chiamata che collega il colpevole se il differito è già risolto.

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>


Alcune cose: 1) Vedo quello che stai dicendo che donenon verrà eseguito se un precedente fatto ha un'eccezione. Ma perché dovrebbe essere ignorato silenziosamente, intendo dire che si è verificata un'eccezione, quindi perché dici che è silenzioso. 2) Disprezzo l' Deferredoggetto perché la sua API è eseguita molto male. È troppo complesso e confuso. Il tuo codice qui non aiuta neanche a dimostrare il tuo punto e ha troppa complessità non necessaria per quello che stai cercando di dimostrare. 3) Perché gli doneindici 2, 4 e 6 vengono eseguiti prima del 2 then?
CodingYoshi

Mio male, sicuramente meriti un voto. Per quanto riguarda il tuo commento sull'eccezione, normalmente è così che funzionano le eccezioni: una volta sollevato, il codice dopo non verrà eseguito. Inoltre, la documentazione di jQuery afferma che verrà eseguita solo se il differito viene risolto.
CodingYoshi

@CodingYoshi La situazione qui è diversa: stavo solo parlando di promesse / rinvii risolti. Non mi sto lamentando che il resto del gestore del successo non viene chiamato, è normale. Ma non vedo alcun motivo per cui un gestore del successo completamente diverso su una promessa di successo non venga chiamato. Tutti .then()saranno chiamati, eccezione (in quei gestori) sollevata o meno. Ma aggiunta / .done()pausa rimanente .
Robert Siemer,

@CodingYoshi Ho notevolmente migliorato la mia risposta, se mi è permesso dire. Codice e testo.
Robert Siemer,

1

C'è un'altra differenza vitale rispetto a jQuery 3.0 che può facilmente portare a comportamenti imprevisti e non è menzionato nelle risposte precedenti:

Considera il seguente codice:

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>

questo produrrà:

then
now

Ora sostituisci done()con then()nello stesso snippet:

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>

l'output è ora:

now
then

Pertanto, per i differiti immediatamente risolti, la funzione passata done()verrà sempre invocata in modo sincrono, mentre qualsiasi argomento passato then()verrà invocato in modo asincrono.

Ciò differisce dalle precedenti versioni di jQuery in cui entrambi i callback venivano chiamati in modo sincrono, come menzionato nella guida all'aggiornamento :

Un altro cambiamento di comportamento richiesto per la conformità a Promises / A + è che i callback differiti .then () vengono sempre chiamati in modo asincrono. In precedenza, se un callback .then () veniva aggiunto a un differito che era già stato risolto o rifiutato, il callback veniva eseguito immediatamente e in modo sincrono.


-5

.done()termina la catena della promessa, assicurandosi che nient'altro possa associare ulteriori passaggi. Ciò significa che l'implementazione della promessa jQuery può generare qualsiasi eccezione non gestita, poiché nessuno può gestirla utilizzando .fail().

In termini pratici, se non si prevede di allegare più passaggi a una promessa, è necessario utilizzare .done(). Per maggiori dettagli vedi perché le promesse devono essere fatte


6
Attenzione! Questa risposta sarebbe corretta per diverse implementazioni promettenti ma non per jQuery, in cui .done()non ha un ruolo conclusivo. La documentazione dice: "Poiché deferred.done () restituisce l'oggetto differito, altri metodi dell'oggetto differito possono essere concatenati a questo, compresi i metodi aggiuntivi .done ()". .fail()non è menzionato ma, sì, anche quello potrebbe essere incatenato.
Roamer-1888,

1
Mio male, non ho controllato il jQuery
gleb bahmutov il

1
@glebbahmutov - forse dovresti eliminare questa risposta in modo che gli altri non si confondano? Solo un suggerimento amichevole :)
Andrey,

2
Per favore non cancellare la risposta, questo può aiutare le persone a chiarire anche i loro fraintendimenti.
Melissa,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.