Devo abster-me de lidar com a rejeição Promise de forma assíncrona?

Acabei de instalar o Nó v7.2.0 e aprendi que o seguinte código:

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

resulta nesta mensagem :;

 (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. 

Eu entendo o raciocínio por trás disso, como muitos programadores provavelmente experimentaram a frustração de um Error acabar sendo engolido por uma Promise . No entanto, fiz esta experiência:

 var prm = Promise.reject(new Error('fail')); setTimeout(() => { prm.catch((err) => { console.log(err.message); }) }, 0) 

o que resulta em:

 (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 

Eu, com base no PromiseRejectionHandledWarning assumo que lidar com uma rejeição Promise assincronamente é / pode ser uma coisa ruim.

Mas por que isso?

“Devo abster-me de lidar com a rejeição Promise de forma assíncrona?”

Esses avisos servem a um propósito importante, mas para ver como tudo funciona, veja esses exemplos:

Tente isto:

 process.on('unhandledRejection', () => {}); process.on('rejectionHandled', () => {}); var prm = Promise.reject(new Error('fail')); setTimeout(() => { prm.catch((err) => { console.log(err.message); }) }, 0); 

Ou isto:

 var prm = Promise.reject(new Error('fail')); prm.catch(() => {}); setTimeout(() => { prm.catch((err) => { console.log(err.message); }) }, 0); 

Ou isto:

 var var caught = require('caught'); var prm = caught(Promise.reject(new Error('fail'))); setTimeout(() => { prm.catch((err) => { console.log(err.message); }) }, 0); 

Disclaimer: Eu sou o autor do módulo pego (e sim, eu escrevi para esta resposta).

Fundamentação

Foi adicionado ao Node como uma das mudanças de quebra entre a v6 e a v7 . Houve uma discussão acalorada sobre isso na edição nº 830: Comportamento de detecção de rejeição não tratada padrão sem acordo universal sobre como promises com manipuladores de rejeição anexadas de forma assíncrona devem se comportar – trabalhe sem avisos, trabalhe com avisos ou seja proibido o uso finalizando o programa . Mais discussões ocorreram em várias edições do projeto de especificação de rejeições não tratadas .

Este aviso é para ajudá-lo a encontrar situações em que você esqueceu de lidar com a rejeição, mas às vezes você pode querer evitá-lo. Por exemplo, você pode querer fazer um monte de pedidos e armazenar as promises resultantes em uma matriz, apenas para lidar com isso mais tarde em alguma outra parte do seu programa.

Uma das vantagens das promises sobre retornos de chamada é que você pode separar o local onde você cria a promise a partir do local (ou lugares) onde você conecta os manipuladores. Esses avisos tornam mais difícil, mas você pode manipular os events (meu primeiro exemplo) ou append um manipulador de captura fictício onde quer que você crie uma promise que não deseja tratar imediatamente (segundo exemplo). Ou você pode ter um módulo para fazer isso por você (terceiro exemplo).

Evitando avisos

Anexar um manipulador vazio não altera a maneira como a promise armazenada funciona de qualquer maneira se você fizer isso em duas etapas:

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

Isso não será o mesmo, embora:

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

Aqui prm2 será uma promise diferente, então prm1 . Enquanto o prm1 será rejeitado com erro ‘falhar’, o prm2 será resolvido com o undefined que provavelmente não é o que você deseja.

Mas você poderia escrever uma function simples para fazer funcionar como um exemplo de dois passos acima, como eu fiz com o módulo caught :

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

Aqui prm3 é o mesmo que prm1 .

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

Atualização de 2017

Consulte também Solicitação de Pull # 6375: lib, src: “throw” em rejeições de promises não tratadas (não fundidas ainda em fevereiro de 2017) marcadas como Milestone 8.0.0 :

Faz promises “lançar” rejeições que saem como erros regulares não detectados . [enfase adicionada]

Isso significa que podemos esperar que o Nó 8.x altere o aviso de que esta questão está relacionada a um erro que causa falha e encerra o processo, e devemos levar isso em consideração enquanto escrevemos nossos programas hoje para evitar surpresas no futuro.

Consulte também o problema de rastreamento Node.js 8.0.0 # 10117 .

Eu assumo que lidar com uma rejeição de Promessa assincronamente é uma coisa ruim.

Sim, de fato é.

Espera-se que você queira lidar com qualquer rejeição imediatamente. Se você não conseguir (e possivelmente não conseguir lidar com isso), receberá um aviso.
Eu experimentei quase todas as situações em que você não iria querer falhar imediatamente após receber uma rejeição. E mesmo que você precise esperar por algo após a falha, você deve fazer isso explicitamente.

Eu nunca vi um caso em que seria impossível instalar imediatamente o manipulador de erros (tente me convencer do contrário). No seu caso, se você quiser uma mensagem de erro um pouco atrasada, basta fazer

 var prm = Promise.reject(new Error('fail')); prm.catch((err) => { setTimeout(() => { console.log(err.message); }, 0); });