Quand le .then (succès, échec) est-il considéré comme un anti-modèle des promesses?

188

J'ai jeté un coup d'œil à la FAQ sur la promesse bluebird , dans laquelle il mentionne que .then(success, fail)c'est un anti-modèle . Je ne comprends pas très bien son explication quant au try and catch. Quel est le problème avec ce qui suit?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

Il semble que l'exemple suggère ce qui suit comme la bonne manière.

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

Quelle est la différence?

user2127480
la source
1
then().catch()est plus lisible, car vous n'avez pas besoin de rechercher une virgule et d'enquêter sur ce rappel pour réussir ou échouer la branche.
Krzysztof Safjanowski
7
@KevinB: Il y a beaucoup de différence, vérifiez les réponses
Bergi
12
@KrzysztofSafjanowski - dévasté par l'argument «semble mieux». Totalement faux!
Andrey Popov
6
REMARQUE: lorsque vous utilisez .catch, vous ne savez pas quelle étape a causé le problème - à l'intérieur de la dernière thenou ailleurs dans la chaîne de promesse. Il a donc son propre inconvénient.
vitaly-t
2
J'ajoute toujours des noms de fonction à la promesse .then () params pour le rendre lisible iesome_promise_call() .then(function fulfilled(res) { logger.log(res) }, function rejected(err) { logger.log(err) })
Shane Rowatt

Réponses:

215

Quelle est la différence?

L' .then()appel renverra une promesse qui sera rejetée au cas où le rappel génère une erreur. Cela signifie que lorsque votre succès loggeréchoue, l'erreur sera transmise au .catch()rappel suivant , mais pas au failrappel associé à success.

Voici un diagramme de flux de contrôle :

diagramme de flux de contrôle de puis avec deux arguments diagramme de flux de contrôle de la chaîne de capture

Pour l'exprimer en code synchrone:

// some_promise_call().then(logger.log, logger.log)
then: {
    try {
        var results = some_call();
    } catch(e) {
        logger.log(e);
        break then;
    } // else
        logger.log(results);
}

Le second log(qui est comme le premier argument .then()) ne sera exécuté que dans le cas où aucune exception ne se produirait. Le bloc étiqueté et l' breakinstruction semblent un peu bizarres, c'est en fait ce que Python a try-except-elsepour (lecture recommandée!).

// some_promise_call().then(logger.log).catch(logger.log)
try {
    var results = some_call();
    logger.log(results);
} catch(e) {
    logger.log(e);
}

L' catchenregistreur gérera également les exceptions de l'appel de l'enregistreur de succès.

Voilà pour la différence.

Je ne comprends pas tout à fait son explication quant au try and catch

L'argument est que vous souhaitez généralement détecter les erreurs à chaque étape du traitement et que vous ne devez pas l'utiliser en chaîne. On s'attend à ce que vous n'ayez qu'un seul gestionnaire final qui gère toutes les erreurs - alors que, lorsque vous utilisez le "antipattern", les erreurs dans certains des rappels ne sont pas gérées.

Cependant, ce modèle est en fait très utile: lorsque vous voulez gérer des erreurs qui se sont produites exactement à cette étape, et que vous voulez faire quelque chose de complètement différent quand aucune erreur ne s'est produite - c'est-à-dire lorsque l'erreur est irrécupérable. Sachez que cela branche votre flux de contrôle. Bien sûr, cela est parfois souhaité.


Quel est le problème avec ce qui suit?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

Que vous deviez répéter votre rappel. Tu veux plutôt

some_promise_call()
   .catch(function(e) {
       return e; // it's OK, we'll just log it
   })
   .done(function(res) {
       logger.log(res);
   });

Vous pouvez également envisager d'utiliser .finally()pour cela.

Bergi
la source
7
c'est l' explication la plus utile que j'ai lue en quelques jours (et j'en ai beaucoup lu). Je ne peux pas expliquer à quel point je suis reconnaissant! :) Je pense que vous devriez souligner plus la différence entre les deux, qui .catchva attraper les erreurs même dans la fonction de succès .. Personnellement, je trouve cela extrêmement mal que vous vous retrouvez avec un point d'entrée d'erreur, qui peut obtenir de multiples erreurs de plusieurs actions, mais c'est mon problème. Quoi qu'il en soit - merci pour l'info! N'avez-vous pas un outil de communication en ligne que vous êtes prêt à partager pour que je puisse demander plus de choses? : P
Andrey Popov
2
J'espère que cela vous donne plus de votes positifs ici. Certainement l'une des meilleures explications d'un Promisemécanicien important sur ce site.
Patrick Roberts
2
.done()ne fait pas partie de la norme, n'est-ce pas? Au moins MDN ne répertorie pas cette méthode. Ce serait utile.
ygoe
1
@ygoe En effet. doneest une chose Bluebird qui était fondamentalement obsolète par then+ détection de rejet non géré.
Bergi
1
juste une note d'un daltonien: les diagrammes n'ont aucun sens :)
Benny K
37

Les deux ne sont pas tout à fait identiques. La différence est que le premier exemple n'attrapera pas une exception levée dans votre successgestionnaire. Donc, si votre méthode ne doit jamais renvoyer que des promesses résolues, comme c'est souvent le cas, vous avez besoin d'un catchgestionnaire de fin (ou encore d'un autre thenavec un successparamètre vide ). Bien sûr, il se peut que votre thengestionnaire ne fasse rien qui puisse potentiellement échouer, auquel cas l'utilisation d'un paramètre à 2 thenpeut être bien.

Mais je pense que le point du texte que vous avez lié est qu'il thenest surtout utile par rapport aux rappels dans sa capacité à enchaîner un tas d'étapes asynchrones, et lorsque vous faites cela, la forme à 2 paramètres de thensubtilement ne se comporte pas comme prévu , pour la raison ci-dessus. Il est particulièrement contre-intuitif lorsqu'il est utilisé à mi-chaîne.

En tant que personne qui a fait beaucoup de trucs asynchrones complexes et qui s'est cogné dans des coins comme celui-ci plus que je ne veux l'admettre, je recommande vraiment d'éviter cet anti-pattern et d'adopter l'approche du gestionnaire séparé.

acjay
la source
18

En examinant les avantages et les inconvénients des deux, nous pouvons faire une estimation calculée de ce qui convient à la situation. Ce sont les deux principales approches de mise en œuvre des promesses. Les deux ont ses avantages et ses inconvénients

Approche de capture

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

Avantages

  1. Toutes les erreurs sont gérées par un bloc catch.
  2. Attrape même toute exception dans le bloc then.
  3. Chaînage de plusieurs rappels de succès

Désavantages

  1. En cas de chaînage, il devient difficile d'afficher différents messages d'erreur.

Approche succès / erreur

some_promise_call()
.then(function success(res) { logger.log(res) },
      function error(err) { logger.log(err) })

Avantages

  1. Vous obtenez un contrôle d'erreur précis.
  2. Vous pouvez avoir une fonction de gestion des erreurs commune pour diverses catégories d'erreurs telles que l'erreur db, l'erreur 500, etc.

Inconvénients

  1. Vous en aurez toujours besoin catchsi vous souhaitez gérer les erreurs générées par le rappel de réussite
aWebDeveloper
la source
Pour quelqu'un qui a besoin de déboguer les problèmes de production en utilisant simplement un fichier journal, je préfère l'approche succès / erreur car elle donne la possibilité de créer une chaîne d'erreur causale qui peut être enregistrée aux limites de sortie de votre application.
Shane Rowatt
question. disons que je passe un appel asynchrone qui fait l'une des choses suivantes: 1) retourne avec succès (2xx statuscode), 2) retourne sans succès (code 4xx ou 5xx) mais pas rejeté en soi, 3) ou ne retourne pas du tout ( la connexion Internet est coupée). Pour le cas n ° 1, le rappel de réussite dans le .then est atteint. Pour le cas n ° 2, le rappel d'erreur dans le .then est atteint. Pour le cas n ° 3, le .catch est appelé. C'est une analyse correcte, non? Le cas n ° 2 est le plus délicat, car techniquement, un 4xx ou un 5xx n'est pas un rejet, il revient toujours avec succès. Ainsi, nous devons le gérer dans le .then. .... Ma compréhension est-elle correcte?
Benjamin Hoffman
"Pour le cas n ° 2, le rappel d'erreur dans le .then est atteint. Pour le cas n ° 3, le .catch est appelé. C'est une analyse correcte, non?" - Voilà comment fonctionne la récupération
aWebDeveloper
2

Expliquez simplement:

Dans ES2018

Lorsque la méthode catch est appelée avec l'argument onRejected, les étapes suivantes sont effectuées:

  1. Que la promesse soit cette valeur.
  2. Revenir ? Invoke (promesse, "alors", "indéfini, onRejected").

cela signifie:

promise.then(f1).catch(f2)

équivaut à

promise.then(f1).then(undefiend, f2)
bitfishxyz
la source
1

L'utilisation .then().catch()vous permet d'activer le chaînage des promesses qui est nécessaire pour exécuter un flux de travail. Vous devrez peut-être lire certaines informations de la base de données, puis vous souhaitez les transmettre à une API asynchrone, puis vous souhaitez manipuler la réponse. Vous souhaiterez peut-être repousser la réponse dans la base de données. Gérer tous ces flux de travail avec votre concept est faisable mais très difficile à gérer. La meilleure solution sera de then().then().then().then().catch()recevoir toutes les erreurs en une seule fois et de conserver la maintenabilité du code.

Jayant Varshney
la source
0

Utiliser then()et catch()aider à enchaîner le succès et le gestionnaire d'échec sur la promesse catch()travaux sur promesse retournés par then(). Il gère,

  1. Si la promesse était rejetée. Voir le n ° 3 sur l'image
  2. Si une erreur s'est produite dans le gestionnaire de succès de then (), entre les numéros de ligne 4 à 7 ci-dessous. Voir # 2.a sur l'image (le rappel d'échec sur then()ne gère pas cela.)
  3. Si une erreur s'est produite dans le gestionnaire d'échec de then (), ligne numéro 8 ci-dessous. Voir # 3.b sur l'image.

1. let promiseRef: Promise = this. aTimetakingTask (false); 2. promiseRef 3. .then( 4. (result) => { 5. /* successfully, resolved promise. 6. Work on data here */ 7. }, 8. (error) => console.log(error) 9. ) 10. .catch( (e) => { 11. /* successfully, resolved promise. 12. Work on data here */ 13. });

entrez la description de l'image ici

Remarque : Plusieurs fois, le gestionnaire d'échec peut ne pas être défini s'il catch()est déjà écrit. EDIT: reject()n'invoque catch()que si le gestionnaire d'erreurs then()n'est pas défini. Remarquez # 3 sur l'image au catch(). Il est appelé lorsque le gestionnaire des lignes 8 et 9 n'est pas défini.

Cela a du sens car la promesse renvoyée par then()n'a pas d'erreur si un rappel en prend soin.

VenCKi
la source
La flèche du numéro 3 au catchrappel semble incorrecte.
Bergi
Merci! Avec un rappel d'erreur défini dans then (), il n'est pas appelé (ligne # 8 et # 9 dans l'extrait de code). # 3 invoque l'une des deux flèches. Cela a du sens car la promesse renvoyée par then () n'a pas d'erreur si un rappel en prend soin. Modifié la réponse!
VenCKi
-1

Au lieu de mots, bon exemple. Code suivant (si première promesse résolue):

Promise.resolve()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

est identique à:

Promise.resolve()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)

Mais avec la première promesse rejetée, ce n'est pas identique:

Promise.reject()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

Promise.reject()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)
ktretyak
la source
4
Cela n'a pas de sens, pouvez-vous supprimer cette réponse? C'est trompeur et distrayant de la bonne réponse.
Andy Ray
@AndyRay, cela n'a pas de sens dans une application réelle, mais cela a du sens de comprendre le travail des promesses.
ktretyak