javascript - Varför behöver min ES6 Promise-avslag tas om hand omedelbart för att undvika ett felmeddelande i konsolen?
Observera: Följande är ett problem som beter sig olika i olika webbläsare. Så kanske är detta ett problem med webbläsarens implementation. Jag skulle uppskatta lite råd ändå.
I min applikation skapar jag ett par löften som jag kanske inte kommer att använda förrän långt fram i tiden. Vilket borde vara okej, det är ju trots allt löften.
Om löftet som lagras undan löses, finns det inget problem. Jag kan använda det så långt fram i tiden jag vill, och så många gånger jag vill. Som väntat.
Om löftet som lagras bort däremot avvisas, uppstår ett problem. Om jag inte hanterar det avvisandet kort efter att det har gjorts (osäker på hur kort efter) kommer ett meddelande i konsolen att dyka upp i Chrome eller Firefox som indikerar att det finns en oavfångad löfteavvisning/fel. IE visar inte det felet.
Så överväg följande kod:
console.log("About to make a promise rejection.");
var foo = Promise.reject(new Error("Foo Rejected Promise."));
console.log("Promise rejection made.");
Observera att det inte finns någon användning eller konsumtion av löftet foo. Det lagras endast för framtida bruk.
Konsolen på IE ser ut så här:
På väg att göra ett löfteavslag.
Löfte avslaget.
Vilket är väntat. Men samma kod på Chrome kommer att ge följande konsol:
På väg att göra ett löfteavslag.
Löftesavslag gjort.
Oupptäckt (i löfte) Fel: Foo avvisade löfte.(…)
Firefox ser nästan likadant ut som Chrome, förutom formuleringen kring felet "uncaught".
Men saken är den att jag tänker hantera detta "fel" mycket senare, vid den tidpunkt då jag använder löftet. Att enbart HA ett avvisat löfte bör inte orsaka ett konsolfel..... det borde hända om jag använder löftet och inte hanterar felet.
För att simulera en "mycket senare" hantering av felet, överväg denna ändring av koden:
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);
Nu i detta fall, "hanterar" vi felet genom att visa något på konsolen efter en timeout. Nu hanterar IE fortfarande detta som jag skulle förvänta mig:
På väg att göra ett löfteavslag.
Löftesavslag gjort.
Visar Foo Avvisat Löfte. 10 sekunder senare.
I det här fallet gör Firefox som IE och visar exakt dessa meddelanden, och ger mig inte ett felaktigt konsolfel.
Chrome ger dock fortfarande det felaktiga felet:
På väg att göra ett löfteavslag.
Löfte avslaget.
Upptaget (i löfte) Fel: Foo avvisade löfte.(…)
Visar Foo Avvisat Löfte. 10 sekunder senare.
Så Chrome klagade både på att mitt fel inte hanterades, och sedan visade det att det var hanterat.
Det verkar som att jag kan kringgå allt detta med följande kod som verkar vara ett hack. I princip gör jag en "falsk" hantering av felet när löftet skapas och hanterar det sedan på det sätt jag vill senare:
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);
Men det här är ful kod.
Min fråga är tvådelad - finns det ett sätt att se på vad Chrome (och till viss del FireFox) gör och tänka på det som en tjänst? För mig verkar det hemskt. Och för det andra, finns det ett bättre sätt att kringgå detta än tricket att låtsas hantera ett fel när man inte gör det?
Tack i förväg.
Svar #1
Ett löfte som har en chans att bli avvisat bör hanteras på samma sätt som undantag.
Det finns inte så många chanser att ett löfte inte kommer att kedjas synkront. Men om det inte gör det, kräver det 'hack-aktig' .catch(() => { }) för att hantera avvisningen synkront.
Chrome löfteavvisningsbeteende placerar detta
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);
i samma båt för felhantering som
try {
throw new Error("Foo"));
setTimeout(() => {
try {
throw new Error("Foo"));
} catch (err) {
console.log("Displaying " + err.message + " 10 seconds later.");
}
}, 10000);
} catch (err) {}
Skulle inte undantaget vänta 10 sekunder på att fångas av nästa try...catch innan det kastas? Det kommer inte att vänta ens en enda tick. Det kräver en 'hackig' try { ... } catch (err) {}-block för att fungera som avsett.
Detta beteende är redan välkänt för Bluebird-biblioteksanvändare sedan länge. Bluebird har justerbar felhantering och möjliggör effektiv felsökning av promises även i stor skala.
Beteendet är också känt inom Angular 2-utveckling, det tvingas för löften med Zone.js-biblioteket.
Eftersom felsökning av löften har begränsat värde och är tillämpligt i utvecklingsmiljöer, är det möjligt att ändra eller inaktivera detta beteende för inhemska löften i produktionsbyggen:
if (typeof DEBUG_PROMISE === 'undefined') {
window.addEventListener('unhandledrejection', (e) => {
e.preventDefault();
console.warn(e.reason);
});
}
Här är mer läsning om ämnet.
Svar #2
Som någon som har arbetat med löften i ett par år nu förstår jag varför detta beteende är inställt i Chrome. Det är lätt att glömma att fånga ett avvisat löfte, och om det finns ett fel i .then() kommer det inte att komma någon avisering och ditt program kommer bara att förbli tyst och blankt. Det kan vara mycket svårt att felsöka.
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
detta kan lätt skapa ett mycket otydligt beteende när man glömmer att fånga felet.
I Q-biblioteket har du funktionen .done() för att faktiskt tvinga fram det som Chrome gör här:
let p = q.resolve("a")
.then(x => x + "b")
.then(y => throwException(y))
.then(z=> z+"c")
.done(); //this will throw an exception.
Så, det är ett känt problem med löften (promises). Chrome har helt enkelt beslutat att göra .done() till standardbeteende, och ber dig uttryckligen använda .catch(_=>{}) om du vill ignorera misslyckade löften. Q har beslutat att göra tyst misslyckande till standard, och ber dig uttryckligen använda .done() om du vill ha ett undantag vid misslyckat löfte. Om du gillar den senare metoden kan du alltid inkludera ett löftebibliotek. Det är en fråga om smak enligt min åsikt.
