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?
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.
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.
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.
choses importantes à comprendre ici
Les deux fonctions then
et catch
renvoient de nouveaux objets de promesse.
Lancer ou rejeter explicitement déplacera la promesse actuelle vers l'état rejeté.
Puisque then
et catch
renvoient de nouveaux objets de promesse, ils peuvent être chaînés.
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.
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
.
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;
});
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.
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!
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);
});