web-dev-qa-db-fra.com

Dois-je m'abstenir de gérer le rejet de Promise de manière asynchrone?

Je viens d'installer Node v7.2.0 et j'ai appris que le code suivant:

var prm = Promise.reject(new Error('fail'));

donne ce message :;

(node:4786) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: fail
(node:4786) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Je comprends le raisonnement derrière cela car de nombreux programmeurs ont probablement connu la frustration d'un Error finissant par être avalé par un Promise. Cependant, j'ai fait cette expérience:

var prm = Promise.reject(new Error('fail'));

setTimeout(() => {
    prm.catch((err) => {
        console.log(err.message);
    })
},
0)

ce qui se traduit par:

(node:4860) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: fail
(node:4860) DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
(node:4860) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
fail

Sur la base de PromiseRejectionHandledWarning, je suppose que gérer un rejet de Promisede manière asynchrone est/pourrait être une mauvaise chose.

Mais pourquoi ça?

18
rabbitco

"Dois-je m'abstenir de gérer le rejet de Promise de manière asynchrone?"

Ces avertissements ont un objectif important, mais pour voir comment tout cela fonctionne, consultez ces exemples:

Essaye ça:

process.on('unhandledRejection', () => {});
process.on('rejectionHandled', () => {});

var prm = Promise.reject(new Error('fail'));

setTimeout(() => {
    prm.catch((err) => {
        console.log(err.message);
    })
}, 0);

Ou ca:

var prm = Promise.reject(new Error('fail'));
prm.catch(() => {});

setTimeout(() => {
    prm.catch((err) => {
        console.log(err.message);
    })
}, 0);

Ou ca:

var var caught = require('caught');
var prm = caught(Promise.reject(new Error('fail')));

setTimeout(() => {
    prm.catch((err) => {
        console.log(err.message);
    })
}, 0);

Avertissement: je suis l'auteur du module catch (et oui, je l'ai écrit pour cette réponse).

Raisonnement

C'était ajouté au Node comme l'un des Briser les changements entre v6 et v7 . Il y a eu une discussion animée à ce sujet dans Problème # 830: Comportement de détection de rejet non géré par défaut sans accord universel sur la façon dont les promesses avec des gestionnaires de rejet attachés de manière asynchrone devraient se comporter - travailler sans avertissements, travailler avec des avertissements ou être interdit de utiliser du tout en mettant fin au programme. Plus de discussions ont eu lieu dans plusieurs numéros du projet nhandled-rejetions-spec .

Cet avertissement est destiné à vous aider à trouver des situations dans lesquelles vous avez oublié de gérer le rejet, mais parfois vous souhaiterez peut-être l'éviter. Par exemple, vous voudrez peut-être faire un tas de demandes et stocker les promesses qui en résultent dans un tableau, pour ensuite les gérer plus tard dans une autre partie de votre programme.

L'un des avantages des promesses par rapport aux rappels est que vous pouvez séparer l'endroit où vous créez la promesse de l'endroit (ou des endroits) où vous attachez les gestionnaires. Ces avertissements rendent la tâche plus difficile à réaliser, mais vous pouvez soit gérer les événements (mon premier exemple), soit attacher un gestionnaire de capture fictif chaque fois que vous créez une promesse que vous ne voulez pas gérer tout de suite (deuxième exemple). Ou vous pouvez demander à un module de le faire pour vous (troisième exemple).

Éviter les avertissements

Attacher un gestionnaire vide ne change en rien le fonctionnement de la promesse stockée si vous le faites en deux étapes:

var prm1 = Promise.reject(new Error('fail'));
prm1.catch(() => {});

Ce ne sera pas la même chose, cependant:

var prm2 = Promise.reject(new Error('fail')).catch(() => {});

Ici prm2 sera une autre promesse que prm1. Tandis que prm1 sera rejeté avec l'erreur "échec", prm2 sera résolu avec undefined ce qui n'est probablement pas ce que vous voulez.

Mais vous pouvez écrire une fonction simple pour la faire fonctionner comme un exemple en deux étapes ci-dessus, comme je l'ai fait avec le module caught:

var prm3 = caught(Promise.reject(new Error('fail')));

Ici prm3 est le même que prm1.

Voir: https://www.npmjs.com/package/caught

Mise à jour 2017

Voir aussi Pull Request # 6375: lib, src: "throw" on non-managed promise rejects (pas encore fusionné en février 2017) marqué comme Milestone 8.0. :

Fait des promesses de "rejeter" les rejets qui se terminent comme des erreurs normales non détectées . [emphase ajoutée]

Cela signifie que nous pouvons nous attendre à Node 8.x pour changer l'avertissement que cette question est sur le point en une erreur qui plante et met fin au processus et nous devrions en tenir compte lors de l'écriture de nos programmes aujourd'hui pour éviter les surprises à l'avenir.

Voir aussi Node.js 8.0.0 Tracking Issue # 10117 .

23
rsp

Je suppose que le traitement asynchrone d'un rejet Promise est une mauvaise chose.

Oui, en effet.

Il est prévu que vous voulez pour gérer tout rejet immédiatement de toute façon. Si vous ne parvenez pas à le faire (et éventuellement à ne jamais le gérer), vous obtiendrez un avertissement.
Je n'ai connu pratiquement aucune situation où vous ne voudriez pas échouer immédiatement après avoir été rejeté. Et même si vous devez attendre quelque chose de plus après l'échec, vous devez le faire explicitement.

Je n'ai jamais vu de cas où il serait impossible d'installer immédiatement le gestionnaire d'erreurs (essayez de me convaincre du contraire). Dans votre cas, si vous voulez un message d'erreur légèrement retardé, faites simplement

var prm = Promise.reject(new Error('fail'));

prm.catch((err) => {
    setTimeout(() => {
        console.log(err.message);
    }, 0);
});
2
Bergi