Perché non posso lanciare all’interno di un gestore di Promise.catch?

Perché non posso semplicemente lanciare un Error all’interno della richiamata catch e lasciare che il processo gestisca l’errore come se fosse in qualsiasi altro ambito?

Se non faccio console.log(err) niente viene stampato e non so nulla di quello che è successo. Il processo finisce semplicemente …

Esempio:

 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 }); 

Se i callback vengono eseguiti nel thread principale, perché l’ Error viene inghiottito da un buco nero?

Come altri hanno spiegato, il “buco nero” è perché lanciare dentro una .catch continua la catena con una promise respinta, e non si hanno più catture, che portano a una catena non terminata, che inghiotte errori (cattivi!)

Aggiungi un’altra cattura per vedere cosa sta succedendo:

 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! }); 

Un catch nel mezzo di una catena è utile quando vuoi che la catena proceda nonostante un passaggio fallito, ma un re-throw è utile per continuare a fallire dopo aver fatto cose come la registrazione di informazioni o la pulizia, forse anche alterando quale errore viene lanciato

Trucco

Per far apparire l’errore come un errore nella console web, come originariamente inteso, io uso questo trucco:

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

Persino i numeri di riga sopravvivono, quindi il collegamento nella console Web mi porta direttamente al file e alla riga in cui si è verificato l’errore (originale).

Perché funziona

Qualsiasi eccezione in una funzione chiamata come un adempimento di promise o un gestore di rifiuto viene convertita automaticamente in un rifiuto della promise che si suppone debba restituire. Il codice di promise che chiama la tua funzione si occupa di questo.

D’altra parte, una funzione chiamata da setTimeout, viene sempre eseguita dallo stato stabile di JavaScript, ovvero viene eseguita in un nuovo ciclo nel ciclo di eventi di JavaScript. Eccezioni non ci sono catturati da nulla, e lo fanno alla console web. Poiché err contiene tutte le informazioni sull’errore, inclusi lo stack originale, il file e il numero di riga, viene comunque segnalato correttamente.

Cose importanti da capire qui

  1. Entrambe le funzioni then e catch restituiscono nuovi oggetti promessi.

  2. Lanciare o rifiutare esplicitamente, trasferirà la promise corrente allo stato rifiutato.

  3. Da then momento e catch cambio nuovi oggetti promessi, possono essere incatenati.

  4. Se lanci o rifiuti all’interno di un promettente ( then o catch ), questo verrà gestito nel successivo handler di rifiuto lungo il percorso di concatenamento.

  5. Come menzionato da jfriend00, i gestori then e catch non vengono eseguiti in modo sincrono. Quando un handler lancia, finirà immediatamente. Quindi, lo stack verrà svolto e l’eccezione verrà persa. Ecco perché lanciare un’eccezione rifiuta l’attuale promise.


Nel tuo caso, stai rifiutando all’interno di do1 lanciando un object Error . Ora, l’attuale promise sarà in stato di rifiuto e il controllo sarà trasferito al gestore successivo, che è then nel nostro caso.

Poiché il gestore then non ha un gestore di rifiuto, il do2 non verrà eseguito affatto. Puoi confermare questo usando console.log al suo interno. Poiché la promise corrente non ha un gestore di rifiuto, sarà anche respinta con il valore di rifiuto della precedente promise e il controllo sarà trasferito al gestore successivo che è catch .

Poiché catch è un gestore di rifiuto, quando si esegue console.log(err.stack); al suo interno, è ansible vedere la traccia dello stack degli errori. Ora stai lanciando un object Error da questo, quindi anche la promise restituita da catch sarà in stato rifiutato.

Dal momento che non hai collegato alcun gestore di rifiuto alla catch , non sei in grado di osservare il rifiuto.


Puoi dividere la catena e capire meglio, in questo modo

 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); }); 

L’output che otterrai sarà qualcosa di simile

 Promise Promise {  [Error: do1] } Promise1 Promise {  [Error: do1] } 

All’interno del gestore catch 1, ottieni il valore dell’object promise come rifiutato.

Allo stesso modo, la promise restituita dal gestore catch 1, viene respinta anche con lo stesso errore con cui la promise stata respinta e la stiamo osservando nel secondo gestore di catch .

Ho provato il metodo setTimeout() descritto sopra …

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

Fastidiosamente, ho trovato questo completamente non testabile. Poiché genera un errore asincrono, non è ansible racchiuderlo all’interno di un’istruzione try/catch , poiché il catch avrà interrotto l’ascolto per il momento in cui viene generato l’errore.

Sono tornato a utilizzare solo un ascoltatore che ha funzionato perfettamente e, poiché è il modo in cui JavaScript è destinato a essere utilizzato, è stato altamente verificabile.

 return new Promise((resolve, reject) => { reject("err"); }).catch(err => { this.emit("uncaughtException", err); /* Throw so the promise is still rejected for testing */ throw err; }); 

Secondo le specifiche (vedi 3.III.d) :

d. Se la chiamata allora genera un’eccezione e,
un. Se resolvePromise o rejectPromise sono stati chiamati, ignoralo.
b. Altrimenti, respingi la promise con e come ragione.

Ciò significa che se si lancia un’eccezione nella funzione then , verrà catturato e la tua promise verrà respinta. catch non ha senso qui, è solo una scorciatoia per .then(null, function() {})

Immagino tu voglia registrare i rifiuti non gestiti nel tuo codice. La maggior parte delle biblioteche promesse licenzia una unhandledRejection per questo. Ecco una sintesi pertinente con una discussione al riguardo.

Sì promette errori di inghiottire e puoi catturarli solo con .catch , come spiegato più dettagliatamente in altre risposte. Se si è in Node.js e si desidera riprodurre il normale comportamento di throw , stampare la traccia di stack sul processo di console e di uscita, è ansible eseguire

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

So che è un po ‘tardi, ma mi sono imbattuto in questa discussione, e nessuna delle soluzioni è stata facile da implementare per me, quindi sono venuto con il mio:

Ho aggiunto una piccola funzione di supporto che restituisce una promise, in questo modo:

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

Quindi, se ho un posto specifico in una qualsiasi delle mie promesse catena in cui voglio lanciare un errore (e respingere la promise), semplicemente ritorna dalla funzione precedente con il mio errore costruito, in questo modo:

 }).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); }) 

In questo modo ho il controllo di generare ulteriori errori dall’interno della catena di promesse. Se si desidera gestire anche gli errori di promise “normale”, si espanderebbe il pescato per trattare gli errori “auto-lanciati” separatamente.

Spero che questo aiuti, è la mia prima risposta StackOverflow!