javascript - from - ¿Por qué no puedo tirar dentro de un controlador Promise.catch?
promise.all typescript (6)
Como otros han explicado, el "agujero negro" se debe a que arrojar dentro de un
.catch
continúa la cadena con una promesa rechazada, y no tiene más capturas, lo que lleva a una cadena sin terminar, que se traga los errores (¡malo!)
Agregue una captura más para ver qué sucede:
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!
});
Una captura en el medio de una cadena es útil cuando desea que la cadena continúe a pesar de un paso fallido, pero un relanzamiento es útil para continuar fallando después de hacer cosas como el registro de información o los pasos de limpieza, tal vez incluso alterando qué error es aventado.
Truco
Para hacer que el error aparezca como un error en la consola web, como originalmente pretendías, utilizo este truco:
.catch(function(err) { setTimeout(function() { throw err; }); });
Incluso los números de línea sobreviven, por lo que el enlace en la consola web me lleva directamente al archivo y la línea donde ocurrió el error (original).
Por que funciona
Cualquier excepción en una función llamada como cumplimiento de promesa o controlador de rechazo se convierte automáticamente en un rechazo de la promesa que se supone que debe devolver. El código de promesa que llama a su función se encarga de esto.
Una función llamada por setTimeout, por otro lado, siempre se ejecuta desde el estado estable de JavaScript, es decir, se ejecuta en un nuevo ciclo en el bucle de eventos de JavaScript.
Las excepciones no están atrapadas por nada y llegan a la consola web.
Dado que
err
contiene toda la información sobre el error, incluida la pila original, el archivo y el número de línea, aún se informa correctamente.
¿Por qué no puedo simplemente lanzar un
Error
dentro de la devolución de llamada y dejar que el proceso maneje el error como si estuviera en cualquier otro ámbito?
Si no hago
console.log(err)
no se imprime nada y no sé nada de lo que sucedió.
El proceso acaba de terminar ...
Ejemplo:
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
});
Si las devoluciones de llamada se ejecutan en el hilo principal, ¿por qué el
Error
es tragado por un agujero negro?
De acuerdo con las especificaciones (ver 3.III.d) :
re. Si llama, arroja una excepción e,
a. Si se ha llamado a resolverPromesa o rechazarPromesa, ignórela.
si. De lo contrario, rechace la promesa con e como la razón.
Eso significa que si arroja una excepción en la función, se detectará y su promesa será rechazada.
catch
no tiene sentido aquí, es solo un atajo para
.then(null, function() {})
Supongo que desea registrar rechazos no controlados en su código.
La mayoría de las bibliotecas de promesas
unhandledRejection
un
unhandledRejection
por ello.
Aquí está
la esencia relevante
con la discusión al respecto.
Sé que esto es un poco tarde, pero me encontré con este hilo, y ninguna de las soluciones fue fácil de implementar para mí, así que se me ocurrió la mía:
Agregué una pequeña función auxiliar que devuelve una promesa, así:
function throw_promise_error (error) {
return new Promise(function (resolve, reject){
reject(error)
})
}
Luego, si tengo un lugar específico en cualquiera de mi cadena de promesas donde quiero lanzar un error (y rechazar la promesa), simplemente regreso de la función anterior con mi error construido, así:
}).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);
})
De esta manera tengo el control de lanzar errores adicionales desde dentro de la cadena de promesa. Si desea manejar también los errores de promesa ''normales'', expandiría su captura para tratar los errores ''autoejecutables'' por separado.
Espero que esto ayude, ¡es mi primera respuesta de !
Sí, promete errores de deglución, y solo puede atraparlos con
.catch
, como se explica con más detalle en otras respuestas.
Si está en Node.js y desea reproducir el comportamiento de
throw
normal, imprimir el seguimiento de la pila en la consola y salir del proceso, puede hacerlo
...
throw new Error(''My error message'');
})
.catch(function (err) {
console.error(err.stack);
process.exit(0);
});
setTimeout()
método
setTimeout()
detallado anteriormente ...
.catch(function(err) { setTimeout(function() { throw err; }); });
Molesto, encontré que esto era completamente inestable.
Debido a que arroja un error asincrónico, no puede envolverlo dentro de una declaración
try/catch
, porque la
catch
habrá dejado de escuchar cuando se produce el error.
Volví a usar un oyente que funcionó perfectamente y, debido a que es la forma en que se debe usar JavaScript, fue altamente comprobable.
return new Promise((resolve, reject) => {
reject("err");
}).catch(err => {
this.emit("uncaughtException", err);
/* Throw so the promise is still rejected for testing */
throw err;
});
Cosas importantes para entender aquí
-
Ambas funciones,
then
ycatch
, devuelven nuevos objetos de promesa. -
Ya sea arrojando o rechazando explícitamente, se moverá la promesa actual al estado rechazado.
-
Desde
then
ycatch
devolver nuevos objetos de promesa, se pueden encadenar. -
Si arroja o rechaza dentro de un controlador de promesa (
then
o locatch
), se manejará en el siguiente controlador de rechazo por el camino de encadenamiento. -
Como mencionó jfriend00, los manejadores
then
ycatch
no se ejecutan sincrónicamente. Cuando un controlador lanza, llegará a su fin de inmediato. Por lo tanto, la pila se desenrollará y se perderá la excepción. Es por eso que lanzar una excepción rechaza la promesa actual.
En su caso, está rechazando dentro de
do1
arrojando un objeto
Error
.
Ahora, la promesa actual estará en estado rechazado y el control se transferirá al siguiente controlador, que es
then
en nuestro caso.
Como el controlador de
then
no tiene un controlador de rechazo, el
do2
no se ejecutará en absoluto.
Puede confirmar esto usando
console.log
dentro de él.
Dado que la promesa actual no tiene un controlador de rechazo, también se rechazará con el valor de rechazo de la promesa anterior y el control se transferirá al siguiente controlador que es
catch
.
Como
catch
es un controlador de rechazo, cuando haces
console.log(err.stack);
dentro de él, puede ver el seguimiento de la pila de errores.
Ahora, está lanzando un objeto
Error
para que la promesa devuelta por
catch
también esté en estado rechazado.
Como no ha adjuntado ningún controlador de rechazo a la
catch
, no puede observar el rechazo.
Puedes dividir la cadena y entender esto mejor, así
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);
});
La salida que obtendrás será algo así como
Promise Promise { <rejected> [Error: do1] }
Promise1 Promise { <rejected> [Error: do1] }
Dentro del controlador de
catch
1, está obteniendo el valor del objeto de
promise
como rechazado.
Del mismo modo, la promesa devuelta por el controlador de
catch
1 también se rechaza con el mismo error con el que se rechazó la
promise
y la estamos observando en el segundo controlador de
catch
.