Dovrei astenermi dal gestire il rifiuto di Promise in modo asincrono?

Ho appena installato Node v7.2.0 e ho imparato che il seguente codice:

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

risultati in questo messaggio :;

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

Capisco il ragionamento alla base di questo, poiché molti programmatori hanno probabilmente sperimentato la frustrazione di un Error finisce per essere inghiottito da una Promise . Comunque, ho fatto questo esperimento:

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

che risulta in:

 (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 

Sulla base del PromiseRejectionHandledWarning supponiamo che gestire un rifiuto di Promise modo asincrono sia / potrebbe essere una cosa negativa.

Ma perché è così?

“Dovrei astenermi dal gestire il rifiuto di Promise in modo asincrono?”

Questi avvertimenti hanno uno scopo importante, ma per vedere come funziona, vedi questi esempi:

Prova questo:

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

O questo:

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

O questo:

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

Disclaimer: Sono l’autore del modulo preso (e sì, l’ho scritto per questa risposta).

Fondamento logico

È stato aggiunto al nodo come uno dei cambiamenti di Breaking tra v6 e v7 . C’è stata un’accesa discussione al riguardo nel numero # 830: comportamento di rilevamento rifiuto non gestito predefinito senza alcun accordo universale su come comportarsi con promesse con gestori di rifiuto asincroni dovrebbe funzionare – funzionare senza avvertimenti, lavorare con avvisi o essere vietato di utilizzare affatto terminando il programma . Altre discussioni hanno avuto luogo in diverse questioni del progetto di non respingimento delle specifiche .

Questo avvertimento ti aiuta a trovare le situazioni in cui hai dimenticato di gestire il rifiuto, ma a volte potresti volerlo evitare. Ad esempio potresti voler fare un mucchio di richieste e memorizzare le promesse risultanti in un array, solo per gestirle successivamente in qualche altra parte del tuo programma.

Uno dei vantaggi delle promesse sui callback è che è ansible separare il luogo in cui si crea la promise dal luogo (o dai luoghi) in cui si collegano i gestori. Questi avvertimenti rendono più difficile la gestione, ma puoi gestire gli eventi (il mio primo esempio) o colbind un gestore fittizio di cattura ovunque crei una promise che non vuoi gestire subito (secondo esempio). Oppure puoi fare in modo che un modulo lo faccia per te (terzo esempio).

Evitare gli avvertimenti

L’associazione di un gestore vuoto non cambia il modo in cui la promise memorizzata funziona in alcun modo se lo si fa in due passaggi:

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

Questo non sarà lo stesso, però:

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

Qui prm2 sarà una promise diversa da prm1 . Mentre prm1 verrà rifiutato con errore “fail”, prm2 verrà risolto con un undefined che probabilmente non è quello che desideri.

Ma potresti scrivere una semplice funzione per farlo funzionare come un esempio in due passaggi sopra, come ho fatto con il modulo caught :

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

Qui prm3 è uguale a prm1 .

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

Aggiornamento 2017

Vedi anche Pull Request # 6375: lib, src: “throw” su respingimenti promessi non gestiti (non ancora uniti a partire da Febryary 2017) contrassegnati come Milestone 8.0.0 :

Fa Promesse “buttare” i rifiuti che escono come normali errori non catturati . [enfasi aggiunta]

Ciò significa che possiamo aspettarci che il Node 8.x cambi l’avviso relativo a questa domanda in un errore che si blocca e termina il processo e dovremmo tenerne conto durante la scrittura dei nostri programmi oggi per evitare sorprese in futuro.

Vedi anche il Node.js 8.0.0 Tracking Issue # 10117 .

Presumo che gestire un rifiuto promesso in modo asincrono sia una cosa negativa.

Sì, lo è davvero.

Si prevede che si desideri gestire eventuali rifiuti immediatamente comunque. Se non si riesce a farlo (e probabilmente non si riuscirà mai a gestirlo), verrà visualizzato un avviso.
Ho provato pochissime situazioni in cui non vorresti fallire subito dopo aver ricevuto un rifiuto. E anche se hai bisogno di aspettare qualcosa di più dopo il fallimento, dovresti farlo esplicitamente.

Non ho mai visto un caso in cui sarebbe imansible installare immediatamente il gestore degli errori (prova a convincermi altrimenti). Nel tuo caso, se vuoi un messaggio di errore leggermente ritardato, fallo

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