javascript - cli - ng-if
AngularJS-Promesas de reencuentro atrapado excepciones (5)
En el siguiente código, la función catch de la promesa $ q captura una excepción:
// Fiddle - http://jsfiddle.net/EFpn8/6/
f1().then(function(data) {
console.log("success 1: "+data)
return f2();
})
.then(function(data) {console.log("success 2: "+data)})
.catch(function(data) {console.log("error: "+data)});
function f1() {
var deferred = $q.defer();
// An exception thrown here is not caught in catch
// throw "err";
deferred.resolve("done f1");
return deferred.promise;
}
function f2() {
var deferred = $q.defer();
// An exception thrown here is handled properly
throw "err";
deferred.resolve("done f2");
return deferred.promise;
}
Sin embargo, cuando miro la salida del registro de la consola, veo lo siguiente:
La excepción se detectó en Angular, pero también se detectó mediante el manejo de errores del navegador. Este comportamiento se reproduce con la biblioteca Q.
¿Es un error? ¿Cómo puedo realmente atrapar una excepción con $ q?
¿Es un error?
No. Al buscar en la fuente $ q se revela que se creó un bloque de prueba / captura deliberado para responder a las excepciones lanzadas en la devolución de llamada por
- Rechazando la promesa, como a través de ti habías llamado
deferred.reject
- Llamando al hander de excepción angular registrado. Como puede verse en los documentos $ exceptionHandler , el comportamiento predeterminado de esto es iniciar sesión en la consola del navegador como un error, que es lo que ha observado.
... también fue atrapado por el manejo de errores del navegador
Para aclarar, la excepción no es manejada directamente por el navegador, pero aparece como un error porque Angular ha llamado a console.error
¿Cómo puedo realmente atrapar una excepción con $ q?
Las devoluciones de llamada se ejecutan algún tiempo después, cuando la pila de llamadas actual se ha borrado, por lo que no podrá ajustar la función externa en el bloque try
/ catch
. Sin embargo, tienes 2 opciones:
Coloque en el bloque
try
/catch
alrededor del código que podría generar la excepción, dentro de la devolución de llamada:f1().then(function(data) { try { return f2(); } catch(e) { // Might want convert exception to rejected promise return $q.reject(e); } })
Cambie cómo se comporta el servicio
$exceptionHandler
Angular, como en Cómo anular la implementación de $ exceptionHandler . Podrías cambiarlo para que no haga absolutamente nada, así que nunca habrá nada en el registro de errores de la consola, pero creo que no lo recomendaría.
Corregido con AngularJS versión 1.6
El razonamiento de este comportamiento fue que un error no detectado es diferente de un rechazo regular, ya que puede ser causado por un error de programación, por ejemplo. En la práctica, esto resultó ser confuso o indeseable para los usuarios, ya que ni las promesas nativas ni ninguna otra biblioteca de promesas popular distinguen los errores lanzados de los rechazos regulares. (Nota: si bien este comportamiento no va en contra de la especificación Promises / A +, tampoco está prescrito).
$ q:
Debido a e13eea , un error e13eea desde una promesa en los
onFulfilled
o en laonRejection
se trata exactamente igual que un rechazo regular. Anteriormente, también se pasaría a$exceptionHandler()
(además de rechazar la promesa con el error como motivo).El nuevo comportamiento se aplica a todos los servicios / controladores / filtros, etc. que dependen de
$q
(incluidos los servicios integrados, como$http
y$route
). Por ejemplo,$http''s transformRequest/Response
funciones$http''s transformRequest/Response
o la función redirectTo de una ruta, así como las funciones especificadas en el objeto de resolución de una ruta, ya no darán como resultado una llamada a$exceptionHandler()
si se produce un error. Aparte de eso, todo seguirá comportándose de la misma manera; es decir, se rechazarán las promesas, se cancelará la transición de la ruta, se emitirán los eventos$routeChangeError
, etc.- Guía del desarrollador de AngularJS - Migración de V1.5 a V1.6 - $ q
Aquí hay una prueba de muestra que muestra la nueva función de construcción $ q, uso de .finally (), rechazos y promesas de propagación de cadenas:
iit(''test'',inject(function($q, $timeout){
var finallyCalled = false;
var failValue;
var promise1 = $q.when(true)
.then(function(){
return $q(function(resolve,reject){
// Reject promise1
reject("failed");
});
})
.finally(function(){
// Always called...
finallyCalled = true;
// This will be ignored
return $q.when(''passed'');
});
var promise2 = $q.when(promise1)
.catch(function(value){
// Catch reject of promise1
failValue = value;
// Continue propagation as resolved
return value+1;
// Or continue propagation as rejected
//return $q.reject(value+2);
});
var updateFailValue = function(val){ failValue = val; };
$q.when(promise2)
.then( updateFailValue )
.catch(updateFailValue );
$timeout.flush();
expect( finallyCalled ).toBe(true);
expect( failValue ).toBe(''failed1'');
}));
El aplazado es una forma obsoleta y realmente terrible de construir promesas, el uso del constructor resuelve este problema y más:
// This function is guaranteed to fulfill the promise contract
// of never throwing a synchronous exception, using deferreds manually
// this is virtually impossible to get right
function f1() {
return new Promise(function(resolve, reject) {
// code
});
}
No sé si las promesas angulares apoyan lo anterior, si no, puedes hacer esto:
function createPromise(fn) {
var d = $q.defer();
try {
fn(d.resolve.bind(d), d.reject.bind(d));
}
catch (e) {
d.reject(e);
}
return d.promise;
}
El uso es el mismo que el constructor de promesa:
function f1() {
return createPromise(function(resolve, reject){
// code
});
}
Los $q
Angular usan una convención donde los errores de lanzamiento se registran independientemente de que se detecten. En cambio, si desea señalar un rechazo, debe return $q.reject(...
como tal:
function f2() {
var deferred = $q.defer();
// An exception thrown here is handled properly
return $q.reject(new Error("err"));//throw "err";
deferred.resolve("done f2");
return deferred.promise;
}
Esto es para distinguir rechazos de errores como SyntaxError. Personalmente, es una opción de diseño con la que no estoy de acuerdo, pero es comprensible ya que $q
es muy pequeño, por lo que realmente no se puede construir un mecanismo confiable de detección de rechazo no manejado. En bibliotecas más fuertes como Bluebird, este tipo de cosas no es necesario.
Como nota al margen, nunca, nunca hagas tiros: pierdes los rastros de la pila de esa manera.