Placement des prises AVANT et APRÈS alors

103

J'ai du mal à comprendre la différence entre mettre .catchAVANT et APRÈS alors dans une promesse imbriquée.

Alternative 1:

test1Async(10).then((res) => {
  return test2Async(22)
    .then((res) => {
      return test3Async(100);
    }).catch((err) => {
      throw "ERROR AFTER THEN";
    });
}).then((res) => {
  console.log(res);
}).catch((err) => {
  console.log(err);
});

Alternative 2:

test1Async(10).then((res) => {
   return test2Async(22)
     .catch((err) => {
        throw "ERROR BEFORE THEN";
      })
      .then((res) => {
        return test3Async(100);
      });
  }).then((res) => {
    console.log(res);
  }).catch((err) => {
    console.log(err);
  });

Le comportement de chaque fonction est le suivant, test1 échoue si nombre est <0test2 échoue si nombre est > 10et test3 échoue si nombre ne l'est pas 100. Dans ce cas, test2 échoue seulement.

J'ai essayé d'exécuter et de faire échouer test2Async, AVANT et APRÈS, puis se comporte de la même manière et cela n'exécute pas le test3Async. Quelqu'un peut-il m'expliquer la principale différence pour placer les prises à différents endroits?

Dans chaque fonction je console.log('Running test X')pour vérifier si elle est exécutée.

Cette question se pose à cause du fil de discussion précédent que j'ai posté Comment transformer un rappel imbriqué en promesse? . Je pense que c'est un problème différent et qu'il vaut la peine de publier un autre sujet.

Zanko
la source
les deux .then et .catch peuvent altérer la promesse ... donc je ne sais pas d'où vient le malentendu. Si vous mettez catch avant le .then, il catch les rejets qui se sont produits avant le .then et le .then exécutera ses rappels terminés / échoués en fonction de ce qui se passe dans le .catch, et vice versa lorsque vous les échangez.
Kevin B
Désolé si ma question n'était pas claire. Mais dans ce cas, comme je l'ai dit, les deux cas se comportent de la même manière, donc je ne vois pas la différence. Peux-tu me dire quand on met catch AVANT et quand on a décidé de le mettre APRÈS alors? le mettre après semble vraiment intuitif et courant. Je ne sais pas pourquoi parfois nous le mettons avant
Zanko
S'ils font la même chose, c'est simplement parce que ce que chacun fait ne modifie pas le résultat dans ce cas précis. Un changement mineur dans l'un ou l'autre pourrait modifier le résultat.
Kevin B
Que voulez-vous dire "modifier le résultat". Désolé je suis vraiment confuse haha
Zanko
Par exemple, si au lieu de lancer une erreur vous ne faisiez rien, la promesse passerait du rejet à la résolution. Cela modifierait bien sûr le résultat, car la promesse est désormais une promesse résolue plutôt qu'une promesse rejetée. (à moins bien sûr qu'il n'ait déjà été résolu, auquel cas la capture n'aurait pas eu lieu de toute façon)
Kevin B

Réponses:

237

Donc, en gros, vous demandez quelle est la différence entre ces deux (où pest une promesse créée à partir d'un code précédent):

return p.then(...).catch(...);

et

return p.catch(...).then(...);

Il existe des différences lorsque p résout ou rejette, mais que ces différences comptent ou non dépend de ce que fait le code à l'intérieur des gestionnaires .then()ou .catch().

Que se passe-t-il lorsque présout:

Dans le premier schéma, lors de la présolution, le .then()gestionnaire est appelé. Si ce .then()gestionnaire renvoie une valeur ou une autre promesse qui finit par se résoudre, alors le .catch()gestionnaire est ignoré. Mais, si le .then()gestionnaire lève ou retourne une promesse qui finalement rejette, alors le .catch()gestionnaire exécutera à la fois un rejet dans la promesse d'origine p, mais aussi une erreur qui se produit dans le .then()gestionnaire.

Dans le second schéma, lors de la présolution, le .then()gestionnaire est appelé. Si ce .then()gestionnaire lève ou retourne une promesse qui finit par rejeter, alors le .catch()gestionnaire ne peut pas l'attraper car c'est avant lui dans la chaîne.

Donc, c'est la différence n ° 1. Si le .catch()gestionnaire est AFTER, il peut également intercepter des erreurs à l'intérieur du .then()gestionnaire.

Que se passe-t-il en cas de prejet:

Maintenant, dans le premier schéma, si la promesse est prejetée, alors le .then()gestionnaire est ignoré et le .catch()gestionnaire sera appelé comme prévu. Ce que vous faites dans le .catch()gestionnaire détermine ce qui est renvoyé comme résultat final. Si vous retournez simplement une valeur du .catch()gestionnaire ou si vous retournez une promesse qui se résout finalement, la chaîne de promesse passe à l'état résolu car vous avez "géré" l'erreur et l'avez renvoyée normalement. Si vous lancez ou renvoyez une promesse rejetée dans le .catch()gestionnaire, la promesse retournée reste rejetée.

Dans le second schéma, si la promesse est prejetée, le .catch()gestionnaire est appelé. Si vous retournez une valeur normale ou une promesse qui se résout finalement à partir du .catch()gestionnaire ("gérant" ainsi l'erreur), alors la chaîne de promesse passe à l'état résolu et le .then()gestionnaire après le .catch()sera appelé.

Voilà donc la différence n ° 2. Si le .catch()gestionnaire est BEFORE, il peut gérer l'erreur et autoriser le .then()gestionnaire à être appelé.

Quand utiliser lequel:

Utilisez le premier schéma si vous ne voulez qu'un seul .catch()gestionnaire qui puisse intercepter les erreurs dans la promesse d'origine pou dans le .then()gestionnaire et un rejet de pdevrait ignorer le .then()gestionnaire.

Utilisez le deuxième schéma si vous voulez être en mesure de détecter les erreurs dans la promesse d'origine pet peut-être (selon les conditions), permettre à la chaîne de promesse de continuer comme résolue, exécutant ainsi le .then()gestionnaire.

L'autre option

Il existe une autre option pour utiliser les deux rappels auxquels vous pouvez passer .then()comme dans:

 p.then(fn1, fn2)

Cela garantit qu'un seul fn1ou fn2sera appelé. Si présout, alors fn1sera appelé. Si prejette, alors fn2sera appelé. Aucun changement de résultat fn1ne peut jamais faire fn2être appelé ou vice versa. Donc, si vous voulez être absolument sûr qu'un seul de vos deux gestionnaires est appelé indépendamment de ce qui se passe dans les gestionnaires eux-mêmes, vous pouvez utiliser p.then(fn1, fn2).

jfriend00
la source
17
La question porte spécifiquement sur l'ordre de .then()et .catch(), auquel vous répondez. De plus, vous donnez quelques astuces pour savoir quand utiliser quel ordre, où je pense qu'il est approprié de mentionner une troisième option, à savoir passer à la fois le gestionnaire de succès et d'erreur à .then () . Dans ce cas, au plus un gestionnaire sera appelé.
ArneHugo
7
@ArneHugo - Bonne suggestion. J'ai ajouté.
jfriend00
Alors, pendant le chaînage de promesses, pouvons-nous écrire .puis .catch .catch .pourrait-on écrire des scénarios?
Kapil Raghuwanshi
@KapilRaghuwanshi, oui, vous pouvez l'utiliser pour passer une valeur par défaut en cas d'échec. ie Promise.reject(new Error("F")).then(x => x).catch(e => {console.log(e); return [1]}).then(console.log)et Promise.resolve([2]).then(x => x).catch(e => [1]).then(console.log)
CervEd
1
@DmitryShvedov - Comme je l'ai deviné, c'est faux .then(this.setState({isModalOpen: false})). Vous ne passez pas une référence de fonction à .then()afin que le code dans les parenthèses soit exécuté immédiatement (avant la résolution de la promesse). Ça devrait être .then(() => this.setState({isModalOpen: false})).
jfriend00
31

La réponse de jfriend00 est excellente, mais j'ai pensé que ce serait une bonne idée d'ajouter le code synchrone analogue.

return p.then(...).catch(...);

est similaire au synchrone:

try {
  iMightThrow() // like `p`
  then()
} catch (err) {
  handleCatch()
}

Si iMightThrow()ne lance pas, then()sera appelé. S'il lance (ou s'il then()lance lui - même), alors handleCatch()sera appelé. Remarquez que le catchbloc n'a aucun contrôle sur l' thenappel ou non .

D'autre part,

return p.catch(...).then(...);

est similaire au synchrone:

try {
  iMightThrow()
} catch (err) {
  handleCatch()
}

then()

Dans ce cas, si iMightThrow()ne lance pas, alors then()s'exécutera. S'il le lance, il appartiendra alors handleCatch()de décider s'il then()est appelé, car s'il est handleCatch()relancé, il then()ne sera pas appelé, car l'exception sera immédiatement renvoyée à l'appelant. Si handleCatch()peut gérer le problème avec élégance, alors then()sera appelé.

akivajgordon
la source
c'est une bonne explication mais vous pourriez envelopper l'orphelin then()dans unfinally{...}
tyskr
2
@ 82Tuskers, êtes-vous sûr? Si je mets then()en finally{...}, ne serait - il être appelé à tort , même si handleCatch()jette? Gardez à l'esprit que mon objectif était de montrer un code synchrone analogue, et non de suggérer différentes façons de gérer les exceptions
akivajgordon
Donc, si nous voulons gérer tous les cas tout en enchaînant .then () serait-il préférable d'utiliser .then (faire quelque chose) .catch (erreur de journal et état de mise à jour) .puis (faire autre chose) .catch (erreur de journal) où nous essayons d'attraper à chaque point mais aussi de continuer à exécuter les stmnts?
anna le