web-dev-qa-db-fra.com

Pourquoi ne puis-je pas jeter à l'intérieur d'un gestionnaire Promise.catch?

Pourquoi ne puis-je pas lancer un Error à l'intérieur du rappel de capture et laisser le processus gérer l'erreur comme s'il s'agissait d'une autre étendue?

Si je ne fais pas console.log(err) rien ne s'imprime et je ne sais rien de ce qui s'est passé. Le processus se termine juste ...

Exemple:

function do1() {
    return new Promise(function(resolve, reject) {
        throw new Error('do1');
        setTimeout(resolve, 1000)
    });
}

function do2() {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            reject(new Error('do2'));
        }, 1000)
    });
}

do1().then(do2).catch(function(err) {
    //console.log(err.stack); // This is the only way to see the stack
    throw err; // This does nothing
});

Si les callbacks sont exécutés dans le thread principal, pourquoi la Error est-elle avalée par un trou noir?

117
demian85

Comme d'autres l'ont expliqué, le "trou noir" est dû au fait que jeter à l'intérieur d'un .catch prolonge la chaîne avec une promesse refusée, et vous n'avez plus de prises, ce qui conduit à une chaîne non terminée, qui engloutit les erreurs (mauvais!)

Ajoutez une capture supplémentaire pour voir ce qui se passe:

do1().then(do2).catch(function(err) {
    //console.log(err.stack); // This is the only way to see the stack
    throw err; // Where does this go?
}).catch(function(err) {
    console.log(err.stack); // It goes here!
});

Une interception au milieu d'une chaîne est utile lorsque vous souhaitez que la chaîne continue malgré une étape manquée, mais qu'une relance est utile pour continuer à échouer après avoir effectué des opérations telles que la journalisation des informations ou le nettoyage étapes, peut-être même modifier quelle erreur est jetée.

Tour

Pour que l'erreur apparaisse comme une erreur dans la console Web, comme vous l'aviez initialement prévu, j'utilise cette astuce:

.catch(function(err) { setTimeout(function() { throw err; }); });

Même les numéros de ligne sont conservés. Le lien dans la console Web m’amène directement au fichier et à la ligne où l’erreur (originale) s’est produite.

Pourquoi ça marche

Toute exception dans une fonction appelée en tant que gestionnaire d'exécution de la promesse ou de rejet est automatiquement convertie en un rejet de la promesse que vous êtes censé retourner. Le code de promesse qui appelle votre fonction s’occupe de cela.

En revanche, une fonction appelée par setTimeout est toujours exécutée à partir de l’état stable JavaScript, c’est-à-dire qu’elle s’exécute dans un nouveau cycle de la boucle d’événements JavaScript. Les exceptions là-bas ne sont pas interceptées et se rendent sur la console Web. Étant donné que err contient toutes les informations sur l'erreur, y compris la pile d'origine, le fichier et le numéro de ligne, l'erreur est toujours signalée correctement.

148
jib

choses importantes à comprendre ici

  1. Les deux fonctions then et catch renvoient de nouveaux objets de promesse.

  2. Lancer ou rejeter explicitement déplacera la promesse actuelle vers l'état rejeté.

  3. Puisque then et catch renvoient de nouveaux objets de promesse, ils peuvent être chaînés.

  4. Si vous lancez ou rejetez à l'intérieur d'un gestionnaire de promesses (then ou catch), il sera traité dans le prochain gestionnaire de rejet le long du chemin de chaînage.

  5. Comme mentionné par jfriend00, les gestionnaires then et catch ne sont pas exécutés de manière synchrone. Quand un manieur lance, il se termine immédiatement. Ainsi, la pile sera déroulée et l'exception sera perdue. C'est pourquoi le lancement d'une exception rejette la promesse actuelle.


Dans votre cas, vous rejetez à l'intérieur de do1 en lançant un objet Error. Maintenant, la promesse actuelle sera dans un état rejeté et le contrôle sera transféré au gestionnaire suivant, qui est then dans notre cas.

Comme le gestionnaire then ne possède pas de gestionnaire de rejet, le do2 ne sera pas exécuté du tout. Vous pouvez le confirmer en utilisant console.log à l'intérieur. Comme la promesse actuelle ne possède pas de gestionnaire de rejet, elle sera également rejetée avec la valeur de rejet de la promesse précédente et le contrôle sera transféré au gestionnaire suivant, qui est catch.

Comme catch est un gestionnaire de rejet, lorsque vous faites console.log(err.stack); à l'intérieur de celui-ci, vous pouvez voir la trace de la pile d'erreur. Vous lancez maintenant un objet Error pour que la promesse renvoyée par catch soit également dans l'état rejeté.

Puisque vous n'avez associé aucun gestionnaire de rejet au catch, vous ne pouvez pas observer le rejet.


Vous pouvez scinder la chaîne et mieux le comprendre, comme ceci

var promise = do1().then(do2);

var promise1 = promise.catch(function (err) {
    console.log("Promise", promise);
    throw err;
});

promise1.catch(function (err) {
    console.log("Promise1", promise1);
});

La sortie que vous obtiendrez sera quelque chose comme

Promise Promise { <rejected> [Error: do1] }
Promise1 Promise { <rejected> [Error: do1] }

Dans le gestionnaire catch 1, la valeur de l'objet promise est rejetée.

De la même manière, la promesse renvoyée par le gestionnaire catch 1 est également rejetée avec la même erreur que celle utilisée pour promise et nous l'observons dans le deuxième gestionnaire catch.

41
thefourtheye

J'ai essayé la méthode setTimeout() détaillée ci-dessus ...

.catch(function(err) { setTimeout(function() { throw err; }); });

Ennuyeux, j'ai trouvé cela complètement indestructible. Étant donné qu'il génère une erreur asynchrone, vous ne pouvez pas l'envelopper dans une instruction try/catch, car la catch aura cessé d'écouter au moment de la génération de l'erreur.

Je suis revenu à utiliser uniquement un auditeur qui fonctionnait parfaitement et, comme c'est la manière dont JavaScript est censé être utilisé, était hautement testable.

return new Promise((resolve, reject) => {
    reject("err");
}).catch(err => {
    this.emit("uncaughtException", err);

    /* Throw so the promise is still rejected for testing */
    throw err;
});
3
RiggerTheGeek

Selon la spécification (voir 3.III.d) :

ré. Si vous appelez alors une exception e,
une. Si resolPromise ou rejeterPromise ont été appelés, ignorez-le.
B. Sinon, rejetez la promesse avec e comme raison.

Cela signifie que si vous lancez une exception dans la fonction then, elle sera interceptée et votre promesse sera rejetée. catch n'a pas de sens ici, c'est juste un raccourci pour .then(null, function() {})

Je suppose que vous souhaitez consigner les rejets non gérés dans votre code. La plupart des promesses libèrent une unhandledRejection pour cela. Voici Gist pertinent avec discussion à ce sujet.

2
just-boris

Je sais que c'est un peu tard, mais je suis tombé sur ce fil et aucune des solutions n'a été facile à mettre en œuvre pour moi, alors j'ai créé le mien:

J'ai ajouté une petite fonction d'aide qui renvoie une promesse, comme ceci:

function throw_promise_error (error) {
 return new Promise(function (resolve, reject){
  reject(error)
 })
}

Ensuite, si je souhaite placer une erreur (et rejeter la promesse) dans l'une des chaînes de promesses, je reviens simplement de la fonction ci-dessus avec mon erreur construite, comme suit:

}).then(function (input) {
 if (input === null) {
  let err = {code: 400, reason: 'input provided is null'}
  return throw_promise_error(err)
 } else {
  return noterrorpromise...
 }
}).then(...).catch(function (error) {
 res.status(error.code).send(error.reason);
})

De cette façon, je suis en mesure de générer des erreurs supplémentaires de l'intérieur de la chaîne de promesses. Si vous souhaitez également gérer les erreurs de promesse "normales", vous devez développer votre capture pour traiter séparément les erreurs "auto-déclenchées".

J'espère que cela aide, c'est ma première réponse stackoverflow!

1
nuudles

Oui promet des erreurs d'hirondelle, et vous ne pouvez les attraper qu'avec .catch, comme expliqué plus en détail dans d'autres réponses. Si vous êtes dans Node.js et que vous voulez reproduire le comportement normal throw, en imprimant la trace de pile sur la console et le processus de sortie, vous pouvez le faire.

...
  throw new Error('My error message');
})
.catch(function (err) {
  console.error(err.stack);
  process.exit(0);
});
0
Jesús Carrera