jQuery différés et promesses - .then () vs .done ()

473

J'ai lu sur les différés et les promesses de jQuery et je ne vois pas la différence entre l'utilisation de .then()& .done()pour des rappels réussis. Je sais que Eric Hynds le mentionne .done()et .success()mappe à la même fonctionnalité, mais je suppose que c'est le cas, .then()car tous les rappels sont tous invoqués à la fin d'une opération réussie.

Quelqu'un peut-il m'éclairer sur l'utilisation correcte?

screenm0nkey
la source
15
Veuillez noter que tout le monde que JQuery 3.0 sorti en juin 2016 était la première version conforme aux spécifications Promises / A + et ES2015 Promises. La mise en œuvre antérieure à cela avait des incompatibilités avec ce que les promesses étaient censées tenir.
Flimm
J'ai mis à jour ma réponse avec une meilleure recommandation de quoi utiliser quand.
Robert Siemer

Réponses:

577

Les rappels associés à done()seront renvoyés lorsque le différé est résolu. Les rappels associés à fail()seront renvoyés lorsque le différé est rejeté.

Avant jQuery 1.8, then()était juste du sucre syntaxique:

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

Depuis la version 1.8, then()est un alias pipe()et renvoie une nouvelle promesse, voir ici pour plus d'informations pipe().

success()et error()ne sont disponibles que sur l' jqXHRobjet renvoyé par un appel à ajax(). Ce sont des alias simples pour done()et fail()respectivement:

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

De plus, done()n'est pas limité à un seul rappel et filtrera les non-fonctions (bien qu'il y ait un bug avec des chaînes dans la version 1.8 qui devrait être corrigé dans 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 );

Il en va de même fail().

Julian Aubourg
la source
8
thenretourner une nouvelle promesse était une chose clé qui me manquait. Je ne pouvais pas comprendre pourquoi une chaîne comme $.get(....).done(function(data1) { return $.get(...) }).done(function(data2) { ... })échouait avec data2undefined; quand je suis passé doneà thença a fonctionné, parce que je voulais vraiment diriger les promesses ensemble plutôt que d'attacher plus de gestionnaires à la promesse d'origine.
wrschneider
5
jQuery 3.0 est la première version conforme aux spécifications Promises / A + et ES2015.
Flimm
4
Je ne comprends toujours pas pourquoi j'utiliserais l'un sur l'autre. Si j'effectue un appel ajax et que je dois attendre que cet appel soit entièrement terminé (ce qui signifie que la réponse est renvoyée par le serveur) avant d'appeler un autre appel ajax, dois-je utiliser doneou then? Pourquoi?
CodingYoshi
@CodingYoshi Consultez ma réponse pour enfin répondre à cette question (utiliser .then()).
Robert Siemer
413

Il y a aussi une différence dans la façon dont les résultats de retour sont traités (son appelé chaînage, donene chaîne pas tout en thenproduisant des chaînes d'appel)

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

Les résultats suivants seront enregistrés:

abc
123
undefined

Tandis que

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

obtiendra ce qui suit:

abc
abc
abc

---------- Mise à jour:

Btw. J'ai oublié de mentionner que si vous renvoyez une promesse au lieu d'une valeur de type atomique, la promesse extérieure attendra que la promesse intérieure soit résolue:

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

de cette façon, il devient très simple de composer des opérations asynchrones parallèles ou séquentielles telles que:

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

Le code ci-dessus émet deux requêtes http en parallèle, ce qui rend les requêtes terminées plus tôt, tandis qu'en dessous de ces requêtes http sont exécutées séquentiellement, réduisant ainsi la charge du serveur

// 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
la source
121
+1 pour la notion qui donene fait rien au résultat où thenchange le résultat. Énorme point raté par les autres imo.
Shanimal
9
Il vaut probablement la peine de mentionner à quelle version de jQuery cela s'applique, puisque le comportement de a thenchangé en 1.8
bradley.ayers
4
+1 Droit au but. J'ai créé un exemple exécutable si quelqu'un veut voir quelles chaînes avec des appels mixtes doneet des thenrésultats.
Michael Kropat
7
l'exemple ci-dessus met également en évidence que «done» fonctionne sur l'objet de promesse d'origine créé initialement mais «ensuite» renvoie une nouvelle promesse.
Pulak Kanti Bhattacharyya
2
Cela s'applique à jQuery 1.8+. Les versions plus anciennes agissent exactement comme l' doneexemple. Passez thenà la version pipeantérieure à 1.8 pour obtenir le thencomportement 1.8+ .
David Harkness
57

.done() n'a qu'un seul rappel et c'est le rappel de réussite

.then() a à la fois des rappels de réussite et d'échec

.fail() n'a qu'un seul échec de rappel

c'est à vous de décider ce que vous devez faire ... vous souciez-vous si cela réussit ou s'il échoue?

Marino Šimić
la source
18
Vous omettez de mentionner que «alors» produit des chaînes d'appels. Voir la réponse de Lu4.
oligofren
Votre réponse est de 2011 ... De nos jours, leurs valeurs de retour sont then()très différentes de done(). Comme then()on l'appelle souvent uniquement avec le rappel de réussite, votre point est plutôt un détail que la chose principale à retenir / savoir. (Je ne peux pas dire comment c'était avant jQuery 3.0.)
Robert Siemer
14

deferred.done ()

ajoute des gestionnaires à appeler uniquement lorsque la résolution différée est résolue . Vous pouvez ajouter plusieurs rappels à appeler.

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

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

Vous pouvez également écrire ci-dessus comme ceci,

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

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

deferred.then ()

ajoute des gestionnaires à appeler lorsque le report est résolu, rejeté ou encore en cours .

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
la source
votre message ne précise pas comment thense comporte si aucun failrappel n'est fourni - à savoir ne pas capturer le failcas du tout
BM
Le cas d'échec déclenche une exception qui peut être interceptée par le niveau supérieur du programme. Vous pouvez également voir l'exception dans la console JavaScript.
David Spector
10

Il y a en fait une différence assez critique, dans la mesure où les différés de jQuery sont censés être des implémentations de Promises (et jQuery3.0 essaie en fait de les mettre en spécification).

La principale différence entre done / then est que

  • .done() TOUJOURS renvoie les mêmes valeurs promises / encapsulées avec lesquelles il a commencé, indépendamment de ce que vous faites ou de ce que vous retournez.
  • .then() renvoie toujours une NOUVELLE promesse, et vous êtes en charge de contrôler ce que cette promesse est basée sur ce que la fonction que vous avez passée l'a renvoyée.

Traduit de jQuery en promesses natives ES2015, .done()c'est un peu comme implémenter une structure "tap" autour d'une fonction dans une chaîne Promise, en ce sens que, si la chaîne est à l'état "résoudre", transmettre une valeur à une fonction. mais le résultat de cette fonction n'affectera PAS la chaîne elle-même.

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

Ceux-ci enregistreront tous les deux 5, pas 6.

Notez que j'ai utilisé done et doneWrap pour faire la journalisation, pas .then. C'est parce que les fonctions console.log ne renvoient en fait rien. Et que se passe-t-il si vous passez. Puis une fonction qui ne renvoie rien?

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

Cela enregistrera:

5

indéfini

Qu'est-il arrivé? Lorsque j'ai utilisé .then et que je lui ai transmis une fonction qui ne renvoyait rien, son résultat implicite était "non défini" ... ce qui bien sûr renvoyait une promesse [non définie] à la méthode then suivante, qui se connectait non définie. La valeur d'origine avec laquelle nous avons commencé a donc été essentiellement perdue.

.then()est, au fond, une forme de composition de fonction: le résultat de chaque étape est utilisé comme argument pour la fonction à l'étape suivante. C'est pourquoi .done est mieux considéré comme un "tap" -> il ne fait pas réellement partie de la composition, juste quelque chose qui jette un œil à la valeur à une certaine étape et exécute une fonction à cette valeur, mais ne modifie pas réellement la composition en aucune façon.

C'est une différence assez fondamentale, et il y a probablement une bonne raison pour laquelle les Promesses natives n'ont pas elles-mêmes implémenté une méthode .done. Nous n'avons pas besoin de comprendre pourquoi il n'y a pas de méthode .fail, car c'est encore plus compliqué (à savoir, .fail / .catch ne sont PAS des miroirs des fonctions .done / .then -> dans .catch qui renvoient des valeurs nues ne le font pas "rester" rejeté comme ceux passés à. puis, ils résolvent!)

Dtipson
la source
6

then()signifie toujours qu'il sera appelé dans tous les cas. Mais les paramètres qui passent sont différents dans les différentes versions de jQuery.

Avant jQuery 1.8, then()est égal à done().fail(). Et toutes les fonctions de rappel partagent les mêmes paramètres.

Mais à partir de jQuery 1.8, then()retourne une nouvelle promesse, et si elle a renvoyé une valeur, elle sera passée dans la prochaine fonction de rappel.

Voyons l'exemple suivant:

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

Avant jQuery 1.8, la réponse devrait être

result = 3
result = 3
result = 3

Tout resultprend 3. Et la then()fonction passe toujours le même objet différé à la fonction suivante.

Mais à partir de jQuery 1.8, le résultat devrait être:

result = 3
result = 7
result = NaN

Parce que la première then()fonction renvoie une nouvelle promesse, et la valeur 7 (et c'est le seul paramètre qui sera transmis) est passée à la suivante done(), donc la deuxième done()écriture result = 7. La seconde then()prend 7 comme valeur de aet prend undefinedcomme valeur de b, donc la seconde then()renvoie une nouvelle promesse avec le paramètre NaN, et la dernière done()affiche NaN comme résultat.

JasmineOT
la source
"then () signifie toujours qu'il sera appelé dans tous les cas" - pas vrai. then () n'est jamais appelé en cas d'erreur à l'intérieur de la promesse.
David Spector
Aspect intéressant qu'un a jQuery.Deferred()puisse recevoir plusieurs valeurs, qu'il transmet correctement à la première .then(). - Un peu étrange cependant ... comme les suivants .then()ne peuvent pas le faire. (L'interface choisie via returnne peut renvoyer qu'une seule valeur.) Le natif de Javascript Promisene fait pas cela. (Ce qui est plus cohérent, pour être honnête.)
Robert Siemer
3

Il y a une cartographie mentale très simple en réponse qui était un peu difficile à trouver dans les autres réponses:

BM
la source
2

Usage unique .then()

Ce sont les inconvénients de .done()

  • ne peut pas être enchaîné
  • bloquer l' resolve()appel (tous les .done()gestionnaires seront exécutés de manière synchrone)
  • resolve()peut obtenir une exception de la part des .done()gestionnaires enregistrés (!)
  • une exception dans un .done()demi-tue le différé:
    • d'autres .done()gestionnaires seront ignorés en silence

J'ai pensé temporairement que cela .then(oneArgOnly)nécessite toujours .catch()pour qu'aucune exception ne soit silencieusement ignorée, mais ce n'est plus vrai: l' unhandledrejectionévénement enregistre les .then()exceptions non gérées sur la console (par défaut). Très raisonnable! Aucune raison de l'utiliser .done().

Preuve

L'extrait de code suivant révèle que:

  • tous les .done()gestionnaires seront appelés synchrones au point deresolve()
    • connecté en tant que 1, 3, 5, 7
    • connecté avant que le script ne tombe en bas
  • exception dans un appelant d' .done()influencesresolve()
    • connecté via catch around resolve()
  • exception rompt la promesse d'une nouvelle .done()résolution
    • 8 et 10 ne sont pas enregistrés!
  • .then() n'a aucun de ces problèmes
    • enregistré en tant que 2, 4, 6, 9, 11 après que le fil est devenu inactif
    • (L'environnement d'extrait n'a pas unhandledrejectionest semble)

Btw, les exceptions de .done()ne peuvent pas être correctement détectées : en raison du modèle synchrone de .done(), l'erreur est soit renvoyée au point de .resolve()(peut être le code de bibliothèque!) Soit à l' .done()appel qui attache le coupable si le différé est déjà résolu.

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
la source
Quelques choses: 1) Je vois ce que vous dites qui donene sera pas exécuté si un précédent fait a une exception. Mais pourquoi serait-il ignoré en silence, je veux dire qu'une exception s'est produite, alors pourquoi dites-vous qu'elle est silencieuse. 2) Je méprise l' Deferredobjet car son API est très très mal faite. C'est trop complexe et déroutant. Votre code ici n'aide pas non plus à prouver votre point et il a trop de complexité inutile pour ce que vous essayez de prouver. 3) Pourquoi les doneindex at 2, 4 et 6 sont-ils exécutés avant le 2 then?
CodingYoshi
Mon mauvais, tu mérite vraiment un vote. Quant à votre commentaire sur l'exception, c'est normalement ainsi que fonctionnent les exceptions: une fois levée, le code après ne sera pas exécuté. De plus, la documentation jQuery indique qu'elle ne sera exécutée que si le différé est résolu.
CodingYoshi
@CodingYoshi La situation est différente ici: je ne parlais que de promesses / différés résolus. Je ne me plains pas que le reste du gestionnaire de réussite ne soit pas appelé, c'est normal. Mais je ne vois aucune raison pour laquelle un gestionnaire de réussite complètement différent sur une promesse réussie n'est pas appelé. Tous .then()seront appelés, exception (dans ces gestionnaires) levée ou non. Mais ajout / .done()pause restante .
Robert Siemer
@CodingYoshi J'ai considérablement amélioré ma réponse, si je peux le dire. Code et texte.
Robert Siemer, le
1

Il existe une autre différence vitale avec jQuery 3.0 qui peut facilement conduire à un comportement inattendu et n'est pas mentionnée dans les réponses précédentes:

Considérez le code suivant:

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>

cela produira:

then
now

Maintenant, remplacez done()par then()dans le même extrait:

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>

la sortie est maintenant:

now
then

Ainsi, pour les différés résolus immédiatement, la fonction passée à done()sera toujours invoquée de manière synchrone, tandis que tout argument passé à then()sera appelé async.

Cela diffère des versions précédentes de jQuery où les deux rappels sont appelés de manière synchrone, comme mentionné dans le guide de mise à niveau :

Un autre changement de comportement requis pour la conformité Promises / A + est que les rappels différés .then () sont toujours appelés de manière asynchrone. Auparavant, si un rappel .then () était ajouté à un différé déjà résolu ou rejeté, le rappel s'exécutait immédiatement et de manière synchrone.

schellmax
la source
-5

.done()met fin à la chaîne de promesse, en s'assurant que rien d'autre ne peut attacher d'autres étapes. Cela signifie que l'implémentation de la promesse jQuery peut lever toute exception non gérée, car personne ne peut la gérer à l'aide de .fail().

En termes pratiques, si vous ne prévoyez pas d'attacher plus d'étapes à une promesse, vous devez utiliser .done(). Pour plus de détails, voyez pourquoi les promesses doivent être faites

gleb bahmutov
la source
6
Mise en garde! Cette réponse serait correcte pour plusieurs implémentations de promesses, mais pas pour jQuery, dans lequel .done()n'a pas de rôle de terminaison. La documentation indique: "Puisque deferred.done () renvoie l'objet différé, d'autres méthodes de l'objet différé peuvent être chaînées à celui-ci, y compris des méthodes .done () supplémentaires". .fail()n'est pas mentionné mais, oui, cela pourrait aussi être enchaîné.
Roamer-1888 du
1
Mon mauvais, n'a pas vérifié le jQuery
gleb bahmutov
1
@glebbahmutov - vous devriez peut-être supprimer cette réponse afin que les autres ne se trompent pas? Juste une suggestion amicale :)
Andrey
2
Veuillez ne pas supprimer la réponse, cela peut aussi aider les gens à dissiper leurs malentendus.
Melissa