¿Cuándo se considera .then (éxito, fracaso) un antipatrón para las promesas?

188

Eché un vistazo a las preguntas frecuentes sobre la promesa de bluebird , en la que menciona que .then(success, fail)es un antipatrón . No entiendo bien su explicación en cuanto al intento y la captura. ¿Qué tiene de malo lo siguiente?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

Parece que el ejemplo sugiere lo siguiente como la forma correcta.

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

¿Cual es la diferencia?

user2127480
fuente
1
then().catch()es más legible, ya que no necesita buscar comas e investigar si esta devolución de llamada es correcta o incorrecta.
Krzysztof Safjanowski
77
@KevinB: Hay mucha diferencia, verifique las respuestas
Bergi
12
@KrzysztofSafjanowski: devastado por el argumento de "se ve mejor". ¡Completamente equivocado!
Andrey Popov
66
NOTA: Cuando está usando .catch, no sabe qué paso causó el problema, dentro del último theno en otro lugar de la cadena de promesa. Entonces tiene su propia desventaja.
vitaly-t
2
Siempre agrego nombres de funciones a la promesa .then () params para que sea legible, es decirsome_promise_call() .then(function fulfilled(res) { logger.log(res) }, function rejected(err) { logger.log(err) })
Shane Rowatt

Respuestas:

215

¿Cual es la diferencia?

La .then()llamada devolverá una promesa que será rechazada en caso de que la devolución de llamada arroje un error. Esto significa que, cuando su éxito loggerfalla, el error se pasará a la siguiente .catch()devolución de llamada, pero no a la faildevolución de llamada que va junto con success.

Aquí hay un diagrama de flujo de control :

Diagrama de flujo de control de entonces con dos argumentos Diagrama de flujo de control de la cadena de captura

Para expresarlo en código síncrono:

// some_promise_call().then(logger.log, logger.log)
then: {
    try {
        var results = some_call();
    } catch(e) {
        logger.log(e);
        break then;
    } // else
        logger.log(results);
}

El segundo log(que es como el primer argumento para .then()) solo se ejecutará en caso de que no ocurra ninguna excepción. El bloque etiquetado y la breakdeclaración se sienten un poco extraños, esto es realmente lo que tiene Python try-except-elsepara (¡lectura recomendada!).

// some_promise_call().then(logger.log).catch(logger.log)
try {
    var results = some_call();
    logger.log(results);
} catch(e) {
    logger.log(e);
}

El catchregistrador también manejará las excepciones de la llamada exitosa del registrador.

Demasiado para la diferencia.

No entiendo bien su explicación en cuanto al intento y atrapar

El argumento es que generalmente desea detectar errores en cada paso del procesamiento, y que no debe usarlo en cadenas. La expectativa es que solo tiene un controlador final que maneja todos los errores, mientras que, cuando usa el "antipatrón", los errores en algunas de las devoluciones de llamada no se manejan.

Sin embargo, este patrón es realmente muy útil: cuando desea manejar los errores que ocurrieron exactamente en este paso, y desea hacer algo completamente diferente cuando no ocurrió ningún error, es decir, cuando el error es irrecuperable. Tenga en cuenta que esto está ramificando su flujo de control. Por supuesto, esto a veces se desea.


¿Qué tiene de malo lo siguiente?

some_promise_call()
.then(function(res) { logger.log(res) }, function(err) { logger.log(err) })

Que tenías que repetir tu devolución de llamada. Prefieres querer

some_promise_call()
   .catch(function(e) {
       return e; // it's OK, we'll just log it
   })
   .done(function(res) {
       logger.log(res);
   });

También podría considerar usar .finally()para esto.

Bergi
fuente
77
Esta es la explicación más útil que he leído en unos días (y he leído mucho). ¡No puedo explicar lo agradecido que estoy! :) Creo que debería enfatizar más la diferencia entre los dos, que .catchva a detectar errores, incluso dentro de la función del éxito .. En lo personal, creo que este muy mal como usted termina con un punto de errores de entrada, que se puede obtener a partir de múltiples errores múltiples acciones, pero este es mi problema. ¡De todas formas, gracias por la información! ¿No tiene alguna herramienta de comunicación en línea que esté dispuesto a compartir para que pueda preguntar algunas cosas más? : P
Andrey Popov
2
Espero que esto te esté dando más votos a favor aquí. Definitivamente una de las mejores explicaciones de un Promisemecánico importante en este sitio.
Patrick Roberts
2
.done()no es parte del estándar, ¿verdad? Al menos MDN no enumera ese método. Seria útil.
Ygoe
1
@ygoe De hecho. donees una cosa de Bluebird que básicamente fue desaprobada por la thendetección de rechazo no controlado.
Bergi
1
Sólo una nota de un daltónico: los diagramas no tienen sentido :)
Benny K
37

Los dos no son idénticos. La diferencia es que el primer ejemplo no detectará una excepción lanzada en su successcontrolador. Entonces, si su método solo debe devolver promesas resueltas, como suele ser el caso, necesita un catchcontrolador final (u otro thencon un successparámetro vacío ). Claro, puede ser que su thencontrolador no haga nada que pueda fallar, en cuyo caso usar un parámetro de 2 thenpodría estar bien.

Pero creo que el punto del texto al que se vinculó es que thenes principalmente útil frente a las devoluciones de llamada en su capacidad de encadenar un montón de pasos asincrónicos, y cuando realmente hace esto, la forma de 2 parámetros de thensutilmente no se comporta como se esperaba , por la razón anterior. Es particularmente contraintuitivo cuando se usa a mitad de cadena.

Como alguien que ha hecho muchas cosas complejas asincrónicas y se ha topado con rincones como este más de lo que me gustaría admitir, realmente recomiendo evitar este antipatrón e ir con el enfoque de controlador separado.

acjay
fuente
18

Al observar las ventajas y desventajas de ambos, podemos hacer una suposición calculada sobre cuál es la adecuada para la situación. Estos son los dos enfoques principales para implementar las promesas. Ambos tienen sus ventajas y desventajas

Enfoque de captura

some_promise_call()
.then(function(res) { logger.log(res) })
.catch(function(err) { logger.log(err) })

Ventajas

  1. Todos los errores son manejados por un bloque de captura.
  2. Incluso atrapa cualquier excepción en el bloque de entonces.
  3. Encadenamiento de múltiples devoluciones de llamadas exitosas

Desventajas

  1. En caso de encadenamiento, se hace difícil mostrar diferentes mensajes de error.

Enfoque de éxito / error

some_promise_call()
.then(function success(res) { logger.log(res) },
      function error(err) { logger.log(err) })

Ventajas

  1. Obtiene un control de error de grano fino.
  2. Puede tener una función común de manejo de errores para varias categorías de errores como error de base de datos, error 500, etc.

Desventajas

  1. Aún necesitará otro catchsi desea manejar los errores arrojados por la devolución de llamada exitosa
aWebDeveloper
fuente
Para alguien que necesita depurar problemas de producción usando solo un archivo de registro, prefiero el Enfoque de éxito / error, ya que brinda la capacidad de crear una cadena de error causal que se puede registrar en los límites de salida de su aplicación.
Shane Rowatt
pregunta. Digamos que hago una llamada asíncrona que hace una de algunas cosas: 1) regresa con éxito (código de estado 2xx), 2) regresa sin éxito (código 4xx o 5xx) pero no rechazado per se, 3) o no regresa en absoluto ( la conexión a internet no funciona). Para el caso # 1, la devolución de llamada exitosa en .then se ve afectada. Para el caso # 2, la devolución de llamada de error en .then es golpeada. Para el caso # 3, se llama a .catch. Este es el análisis correcto, ¿verdad? El caso # 2 es más complicado porque técnicamente un 4xx o 5xx no es un rechazo, todavía regresa con éxito. Por lo tanto, tenemos que manejarlo dentro de .then. .... ¿Es correcto mi entendimiento?
Benjamin Hoffman
"Para el caso # 2, la devolución de llamada de error en .then es golpeada. Para el caso # 3, se llama a .catch. Este es el análisis correcto, ¿verdad?" - Así es como funciona fetch
aWebDeveloper
2

Explicación simple:

En ES2018

Cuando se llama al método catch con el argumento onRejected, se toman los siguientes pasos:

  1. Deje que la promesa sea este valor.
  2. Regreso ? Invocar (promesa, "entonces", «indefinido, en rechazado»).

eso significa:

promise.then(f1).catch(f2)

es igual

promise.then(f1).then(undefiend, f2)
bitfishxyz
fuente
1

El uso le .then().catch()permite habilitar Promise Chaining, que se requiere para completar un flujo de trabajo. Es posible que deba leer alguna información de la base de datos, luego desear pasarla a una API asíncrona y luego manipular la respuesta. Es posible que desee insertar la respuesta nuevamente en la base de datos. Manejar todos estos flujos de trabajo con su concepto es factible pero muy difícil de administrar. La mejor solución será la then().then().then().then().catch()que recibe todos los errores en una sola captura y le permite mantener la facilidad de mantenimiento del código.

Jayant Varshney
fuente
0

Usa then()y catch()ayuda a encadenar el éxito y el controlador de fallas en la promesa. catch()trabaja en promesa devuelto por then(). Lo maneja,

  1. Si la promesa fue rechazada. Ver # 3 en la imagen
  2. Si se produjo un error en el controlador exitoso de then (), entre los números de línea 4 a 7 a continuación. Consulte el n. ° 2.a en la imagen (la devolución de llamada por falla then()no soluciona esto).
  3. Si se produjo un error en el controlador de fallas de entonces (), línea número 8 a continuación. Ver # 3.b en la imagen.

1. let promiseRef: Promise = this. aTimetakingTask (false); 2. promiseRef 3. .then( 4. (result) => { 5. /* successfully, resolved promise. 6. Work on data here */ 7. }, 8. (error) => console.log(error) 9. ) 10. .catch( (e) => { 11. /* successfully, resolved promise. 12. Work on data here */ 13. });

ingrese la descripción de la imagen aquí

Nota : Muchas veces, el controlador de fallas puede no estar definido si catch()ya está escrito. EDITAR: solo se reject()invoca catch()si el controlador de errores nothen() está definido. Aviso # 3 en la imagen a la . Se invoca cuando el manejador en la línea 8 y 9 no están definidos.catch()

Tiene sentido porque la promesa devuelta por then()no tiene un error si una devolución de llamada se está haciendo cargo.

VenCKi
fuente
La flecha del número 3 a la catchdevolución de llamada parece incorrecta.
Bergi
¡Gracias! Con una devolución de llamada de error definida en then (), no se invoca (línea # 8 y # 9 en el fragmento de código). # 3 invoca una de las dos flechas. Tiene sentido porque la promesa devuelta por entonces () no tiene un error si una devolución de llamada se está haciendo cargo. Editado la respuesta!
VenCKi
-1

En lugar de palabras, buen ejemplo. Código siguiente (si se resuelve la primera promesa):

Promise.resolve()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

es idéntico a:

Promise.resolve()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)

Pero con la primera promesa rechazada, esto no es idéntico:

Promise.reject()
.then
(
  () => { throw new Error('Error occurs'); },
  err => console.log('This error is caught:', err)
);

Promise.reject()
.catch
(
  err => console.log('This error is caught:', err)
)
.then
(
  () => { throw new Error('Error occurs'); }
)
ktretyak
fuente
44
Esto no tiene sentido, ¿puedes eliminar esta respuesta? Es engañoso y distrae la respuesta correcta.
Andy Ray
@AndyRay, esto no tiene sentido en una aplicación real, pero tiene sentido comprender el trabajo de las promesas.
ktretyak