Ricerca nel sito web

javascript - Perché il rifiuto della mia Promise ES6 deve essere gestito immediatamente per evitare un messaggio di errore nella console?


Si prega di notare: quanto segue è un problema che si comporta in modo diverso sui vari browser. Quindi forse si tratta di un problema di implementazione del browser. Vorrei comunque qualche consiglio.

Nella mia applicazione, sto creando un paio di promesse che potrei non consumare fino a molto tempo nel futuro. Ciò dovrebbe andare bene, sono promesse, dopotutto.

Se la promessa che viene messa da parte viene risolta, non c'è alcun problema. Posso consumarla in un futuro quanto voglio, e tutte le volte che voglio. Come previsto.

Se la promessa memorizzata viene rifiutata, tuttavia, c'è un problema. A meno che io non gestisca quel rifiuto poco dopo che è stato fatto (non sono sicuro di quanto poco dopo), un messaggio nella console apparirà in Chrome o Firefox indicando che c'è un rifiuto/errore della promessa non catturato. IE non mostra quell'errore.

Quindi considera il seguente codice:

console.log("About to make a promise rejection.");
var foo = Promise.reject(new Error("Foo Rejected Promise."));
console.log("Promise rejection made.");

Si noti che non vi è alcun utilizzo o consumo della promessa foo. Essa viene semplicemente conservata per un uso futuro.

La console su IE appare così:

Sul punto di fare un rifiuto di promessa.

Rifiuto della promessa effettuato.

Cosa è prevedibile. Tuttavia, lo stesso codice su Chrome restituirà la seguente console:

Sul punto di fare un rifiuto di promessa.

Rifiuto della promessa effettuato.

Errore non catturato (nella promessa): Foo Promessa rifiutata.(…)

Firefox assomiglia molto a Chrome, a parte la terminologia usata per l'errore "uncaught".

Ma la cosa è che intendo gestire questo "errore" molto più tardi, al momento in cui consumo la promessa. Il solo AVERE una promessa respinta non dovrebbe causare un errore nella console... questo dovrebbe succedere se consumo la promessa e non gestisco l'errore.

Per simulare una gestione dell'errore "molto più tardi", considera questa modifica del codice:

console.log("About to make a promise rejection.");
var foo = Promise.reject(new Error("Foo Rejected Promise."));
console.log("Promise rejection made.");
setTimeout(() => {
    foo.catch((err) => {
        console.log("Displaying " + err.message + " 10 seconds later.");
    });
}, 10000);

Ora in questo caso, noi "gestiamo" l'errore mostrando qualcosa sulla console dopo un timeout. Ora IE gestisce ancora questo come mi aspetterei:

Sul punto di fare un rifiuto di promessa.

Rifiuto della promessa effettuato.

Visualizzazione di Foo Promessa Rifiutata. 10 secondi dopo.

In questo caso, Firefox funziona come IE e visualizza esattamente questi messaggi, e non mi dà un errore errato nella console.

Chrome, tuttavia, dà ancora l'errore errato:

Sul punto di fare un rifiuto di promessa.

Rifiuto della promessa effettuato.

Errore non catturato (nella promessa): Foo Promessa rifiutata.(…)

Visualizzazione di Foo Promessa Rifiutata. 10 secondi dopo.

Quindi Chrome si è lamentato del fatto che il mio errore non fosse gestito, e poi ha mostrato che era gestito.

Sembra che io possa aggirare tutto questo con il seguente codice che sembra un trucco. Fondamentalmente faccio una gestione "finta" dell'errore quando la promessa viene creata e poi la gestisco realmente nel modo in cui voglio in seguito:

console.log("About to make a promise rejection.");
var foo = Promise.reject(new Error("Foo Rejected Promise."));
foo.catch(() => { });  // Added this hack-ish code.
console.log("Promise rejection made.");
    setTimeout(() => {
    foo.catch((err) => {
        console.log("Displaying " + err.message + " 10 seconds later.");
    });
}, 10000);

Ma questo è codice brutto.

La mia domanda è duplice: c'è un modo di guardare a ciò che Chrome (e in una certa misura FireFox) sta facendo e considerarlo un favore? Perché a me sembra terribile. E in secondo luogo, c'è un modo migliore per aggirare questo problema rispetto al trucco di fingere di gestire un errore quando in realtà non lo si fa?

Grazie in anticipo.

Risposta n. 1

Qualsiasi promessa che ha la possibilità di essere rifiutata, dovrebbe essere gestita in modo simile alle eccezioni.

Non ci sono così tante possibilità che una promessa non venga concatenata in modo asincrono. Ma se non lo sarà, richiede un hack-ish .catch(() => { }) per gestire sincronicamente il rifiuto.

Il comportamento di rifiuto delle promesse di Chrome mette questo

var foo = Promise.reject(new Error("Foo Rejected Promise."));
foo.catch(() => { });  // Added this hack-ish code.

setTimeout(() => {
    foo.catch((err) => {
        console.log("Displaying " + err.message + " 10 seconds later.");
    });
}, 10000);

nella stessa barca della gestione degli errori di

try {
  throw new Error("Foo"));

  setTimeout(() => {
    try {
      throw new Error("Foo"));
    } catch (err) {
      console.log("Displaying " + err.message + " 10 seconds later.");
    }
  }, 10000);
} catch (err) {}

L'eccezione non aspetterebbe 10 secondi per essere catturata dal prossimo try...catch prima di essere lanciata? Non aspetterà neanche un singolo tick. Richiede un blocco try { ... } catch (err) {} 'alla buona' per funzionare come previsto.

Questo comportamento è già ben noto agli utenti della libreria Bluebird da parecchio tempo. Bluebird ha una gestione degli errori regolabile e permette di fare debug delle promesse in modo efficiente anche su larga scala.

Il comportamento è anche noto nello sviluppo di Angular 2, è imposto per le promesse con la libreria Zone.js.

Poiché il debug delle promise ha un valore limitato ed è applicabile all'ambiente di sviluppo, è possibile modificare o disabilitare questo comportamento per le promise native nelle build di produzione:

if (typeof DEBUG_PROMISE === 'undefined') {
  window.addEventListener('unhandledrejection', (e) => {
    e.preventDefault();
    console.warn(e.reason);
  });
}

Ecco ulteriori letture sull'argomento.

Risposta n. 2

Come qualcuno che ha lavorato con le promise per un paio d'anni, capisco perché questo comportamento sia impostato in Chrome. È facile dimenticare di gestire una promise rifiutata, e se c'è un errore in .then(), non ci sarà alcuna notifica e il tuo programma rimarrà semplicemente silenzioso. Può essere molto difficile da debug.

function throwException(c){ return a.b = 2 ;} //a is not defined. throws exception.
let p = q.resolve("a")
         .then(x => x + "b")
         .then(y => throwException(y))
         .then(z=> z+"c");   //this will say nothing in Q library 

questo può facilmente creare un comportamento molto oscuro quando ci si dimentica di gestire l'errore.

Nella libreria Q, hai la funzione .done() per effettivamente imporre ciò che Chrome sta facendo qui:

let p = q.resolve("a")
         .then(x => x + "b")
         .then(y => throwException(y))
         .then(z=> z+"c")
         .done();   //this will throw an exception.

Quindi, è un problema noto nelle promesse. Chrome ha semplicemente deciso di rendere .done() il comportamento predefinito, chiedendoti di usare esplicitamente .catch(_=>{}) se vuoi ignorare le promesse fallite. Q ha deciso di rendere il fallimento silenzioso il comportamento predefinito, chiedendoti di usare esplicitamente .done() se vuoi un'eccezione per una promessa fallita. Se ti piace l'approccio quest'ultimo, puoi sempre includere una libreria di promesse. È una questione di gusto IMHO.