w3schools then promises promesas not from ejemplo catch anidadas javascript asynchronous promise

promises - then is not a function javascript



Cómo regresar desde el bloque catch/then de Promise (3)

Hay muchos tutoriales sobre cómo usar "luego" y "capturar" durante la programación con JavaScript Promise. Sin embargo, todos estos tutoriales parecen perder un punto importante: regresar de un bloque de captura / entonces para romper la cadena Promesa. Comencemos con un código sincrónico para ilustrar este problema:

try { someFunction(); } catch (err) { if (!(err instanceof MyCustomError)) return -1; } someOtherFunction();

En esencia, estoy probando un error detectado y, si no es el error, espero que regrese a la persona que llama, de lo contrario, el programa continuará. Sin embargo, esta lógica no funcionará con Promise:

Promise.resolve(someFunction).then(function() { console.log(''someFunction should throw error''); return -2; }).catch(function(err) { if (err instanceof MyCustomError) { return -1; } }).then(someOtherFunction);

Esta lógica se usa para algunas de las pruebas de mi unidad donde quiero que una función falle de cierta manera. Incluso si cambio la captura a un bloque entonces todavía no soy capaz de romper una serie de Promesas encadenadas porque lo que sea devuelto del bloque entonces / captura se convertirá en una Promesa que se propaga a lo largo de la cadena.

Me pregunto si Promesa es capaz de lograr esta lógica; si no, ¿por qué? Es muy extraño para mí que una cadena Promesa nunca se rompa. ¡Gracias!

Edite el 16/08/2015: de acuerdo con las respuestas dadas hasta ahora, una Promesa rechazada devuelta por el bloque entonces se propagará a través de la cadena Promesa y omitirá todos los bloques subsiguientes hasta que se capture (maneje). Este comportamiento se entiende bien porque simplemente imita el siguiente código sincrónico (enfoque 1):

try { Function1(); Function2(); Function3(); Function4(); } catch (err) { // Assuming this err is thrown in Function1; Function2, Function3 and Function4 will not be executed console.log(err); }

Sin embargo, lo que estaba preguntando es la siguiente situación en el código síncrono (enfoque 2):

try { Function1(); } catch(err) { console.log(err); // Function1''s error return -1; // return immediately } try { Function2(); } catch(err) { console.log(err); } try { Function3(); } catch(err) { console.log(err); } try { Function4(); } catch(err) { console.log(err); }

Me gustaría tratar los errores planteados en diferentes funciones de manera diferente. Es posible que capture todos los errores en un bloque catch como se ilustra en el enfoque 1. Pero de esa manera tengo que hacer una gran declaración de cambio dentro del bloque catch para diferenciar diferentes errores; además, si los errores arrojados por diferentes funciones no tienen un atributo conmutable común, no seré capaz de usar la instrucción switch en absoluto; en tal situación, tengo que usar un bloque de prueba / captura por separado para cada llamada de función. El enfoque 2 a veces es la única opción. ¿Promise no admite este enfoque con su declaración then / catch?


En primer lugar, veo un error común en esta sección del código que podría confundirte por completo. Este es tu bloque de código de muestra:

Promise.resolve(someFunction()).then(function() { console.log(''someFunction should throw error''); return -2; }).catch(function(err) { if (err instanceof MyCustomError) { return -1; } }).then(someOtherFunction());

Necesita referencias de funciones de paso, no llamar a las funciones y pasar su resultado de retorno. Por lo tanto, este código anterior probablemente sea este:

Promise.resolve(someFunction).then(function() { console.log(''someFunction should throw error''); return -2; }).catch(function(err) { if (err instanceof MyCustomError) { // returning a normal value here will take care of the rejection // and continue subsequent processing return -1; } }).then(someOtherFunction);

Tenga en cuenta que he eliminado () después de dos de sus funciones, por lo que solo está pasando la referencia de función, no llamando inmediatamente a la función. Esto permitirá que la infraestructura prometedora decida si llamar a la promesa en el futuro o no. Si cometes este error, te desanimará por cómo están funcionando las promesas porque las cosas se llamarán independientemente.

Tres reglas simples sobre la captura de rechazos.

  1. Si nadie capta el rechazo, se detiene la cadena de promesa inmediatamente y el rechazo original se convierte en el estado final de la promesa. No se invocan controladores posteriores.
  2. Si se capta el rechazo de promesa y no se devuelve nada o se devuelve un valor normal del manejador de rechazo, entonces el rechazo se considera manejado y la cadena de promesa continúa y se invocan los manejadores posteriores. Lo que devuelva del controlador de rechazo se convierte en el valor actual de la promesa y como si el rechazo nunca hubiera sucedido (excepto que este nivel de controlador de resolución no se ha llamado; en su lugar, se ha llamado al manejador de rechazo).
  3. Si se captura el rechazo de promesa y se emite un error del manejador de rechazo o se devuelve una promesa rechazada, todos los manejadores de resolución se saltan hasta el siguiente manejador de rechazo de la cadena. Si no hay controladores de rechazo, la cadena de promesa se detiene y el error recién acuñado se convierte en el estado final de la promesa.

Puede ver un par de ejemplos en este jsFiddle donde muestra tres situaciones:

  1. Al .then() un valor normal de un controlador de rechazo, se .then() al siguiente controlador de resolución .then() (por ejemplo, el procesamiento normal continúa),

  2. Al lanzar un controlador de rechazo, se detiene el procesamiento de resolución normal y todos los manejadores de resolución se omiten hasta llegar a un manejador de rechazo o al final de la cadena. Esta es una forma efectiva de detener la cadena si se encuentra un error inesperado en un controlador de resolución (que creo que es su pregunta).

  3. Al no haber un controlador de rechazo presente, se detiene el procesamiento de resolución normal y todos los manejadores de resolución se saltan hasta llegar a un manejador de rechazo o al final de la cadena.


Esto no se puede lograr con las características del lenguaje. Sin embargo, las soluciones basadas en patrones están disponibles.

Aquí hay dos soluciones.

Retirar error anterior

Este patrón es básicamente sano ...

Promise.resolve() .then(Function1).catch(errorHandler1) .then(Function2).catch(errorHandler2) .then(Function3).catch(errorHandler3) .then(Function4).catch(errorHandler4) .catch(finalErrorHandler);

Promise.resolve() no es estrictamente necesario, pero permite que todas las .then().catch() tengan el mismo patrón, y toda la expresión es más fácil para el ojo.

... pero :

  • si un errorHandler devuelve un resultado, la cadena pasará al manejador de éxito de la siguiente línea.
  • si se genera un errorHandler, la cadena pasará al manejador de errores de la siguiente línea.

El salto deseado de la cadena no ocurrirá a menos que los manejadores de errores estén escritos de manera que puedan distinguir entre un error lanzado previamente y un error recién lanzado. Por ejemplo :

function errorHandler1(error) { if (error instanceof MyCustomError) { // <<<<<<< test for previously thrown error throw error; } else { // do errorHandler1 stuff then // return a result or // throw new MyCustomError() or // throw new Error(), new RangeError() etc. or some other type of custom error. } }

Ahora :

  • si un errorHandler devuelve un resultado, la cadena pasará a la siguiente funciónN.
  • si un errorHandler arroja un MyCustomError, será repetidamente lanzado de nuevo por la cadena y atrapado por el primer controlador de errores que no se ajuste al protocolo if(error instanceof MyCustomError) (por ejemplo, un .catch final ()).
  • si un errorHandler arroja cualquier otro tipo de error, la cadena pasará a la siguiente captura.

Este patrón sería útil si necesita la flexibilidad para saltar al final de la cadena o no, dependiendo del tipo de error lanzado. Raras circunstancias que espero.

DEMO

Capturas aisladas

Otra solución es introducir un mecanismo para mantener cada .catch(errorHandlerN) "aislado" de modo que capture solo los errores que surjan de su correspondiente FunctionN , no de ningún error precedente.

Esto se puede lograr teniendo en la cadena principal solo manejadores de éxito, cada uno de los cuales comprende una función anónima que contiene una subcadena.

Promise.resolve() .then(function() { return Function1().catch(errorHandler1); }) .then(function() { return Function2().catch(errorHandler2); }) .then(function() { return Function3().catch(errorHandler3); }) .then(function() { return Function4().catch(errorHandler4); }) .catch(finalErrorHandler);

Aquí Promise.resolve() juega un papel importante. Sin él, Function1().catch(errorHandler1) estaría en la cadena principal, catch() no estaría aislado de la cadena principal.

Ahora,

  • si un errorHandler devuelve un resultado, la cadena pasará a la siguiente línea.
  • si un errorHandler arroja algo que le gusta, entonces la cadena progresará directamente al finalErrorHandler.

Utilice este patrón si desea omitir siempre hasta el final de la cadena, independientemente del tipo de error arrojado. No se requiere un constructor de error personalizado y no es necesario que los manipuladores de errores se escriban de manera especial.

DEMO

Casos de uso

El patrón que elija estará determinado por las consideraciones ya dadas pero también posiblemente por la naturaleza del equipo de su proyecto.

  • Equipo de una sola persona: usted escribe todo y comprende los problemas, si puede elegir libremente, se ejecuta con sus preferencias personales.
  • Equipo de varias personas: una persona escribe la cadena maestra y varias otras escriben las funciones y sus manejadores de errores. Si puede, opte por las Capturas aisladas, con todo lo que controla la cadena maestra, no necesita imponer la disciplina de escribiendo los manejadores de errores de esa manera.

No hay funcionalidad incorporada para omitir la totalidad de la cadena restante según lo solicite. Sin embargo, puedes imitar este comportamiento arrojando un cierto error a través de cada captura:

doSomething() .then(func1).catch(handleError) .then(func2).catch(handleError) .then(func3).catch(handleError); function handleError(reason) { if (reason instanceof criticalError) { throw reason; } console.info(reason); }

Si alguno de los bloques catch atrapaba un error criticalError , saltaban directamente al final y arrojaban el error. Cualquier otro error sería registrado en la consola y antes de continuar con el próximo bloque .then .