Colocación de la captura ANTES y DESPUÉS

103

Me cuesta entender la diferencia entre poner .catchANTES y DESPUÉS en una promesa anidada.

Alternativa 1:

test1Async(10).then((res) => {
  return test2Async(22)
    .then((res) => {
      return test3Async(100);
    }).catch((err) => {
      throw "ERROR AFTER THEN";
    });
}).then((res) => {
  console.log(res);
}).catch((err) => {
  console.log(err);
});

Alternativa 2:

test1Async(10).then((res) => {
   return test2Async(22)
     .catch((err) => {
        throw "ERROR BEFORE THEN";
      })
      .then((res) => {
        return test3Async(100);
      });
  }).then((res) => {
    console.log(res);
  }).catch((err) => {
    console.log(err);
  });

El comportamiento de cada función es el siguiente, prueba1 falla si número es <0prueba2 falla si número es > 10y prueba3 falla si número no lo es 100. En este caso, test2 solo falla.

Intenté ejecutar y hacer que test2Async fallara, tanto ANTES como DESPUÉS se comporta de la misma manera y eso no está ejecutando el test3Async. ¿Alguien puede explicarme la principal diferencia entre colocar capturas en diferentes lugares?

En cada función, console.log('Running test X')para verificar si se ejecuta.

Esta pregunta surge debido al hilo anterior que publiqué ¿Cómo convertir la devolución de llamada anidada en una promesa? . Me imagino que es un problema diferente y vale la pena publicar otro tema.

Zanko
fuente
tanto .then y .catch pueden alterar la promesa ... así que no estoy seguro de dónde proviene el malentendido. Si coloca catch antes del .then, detectará los rechazos que ocurrieron antes del .then y el .then ejecutará sus devoluciones de llamada hechas / fallidas en función de lo que sucede dentro del .catch, y viceversa cuando los intercambia.
Kevin B
Lo siento si mi pregunta no fue clara. Pero en este caso, como dije, ambos casos se comportan igual, por lo que no puedo ver la diferencia. ¿Puedes decirme cuándo ponemos la captura ANTES y cuándo decidimos ponerla DESPUÉS? ponerlo después parece muy intuitivo y común. No estoy seguro de por qué a veces lo ponemos antes de esa fecha
Zanko
Si realizan lo mismo, es simplemente porque lo que cada uno hace no altera el resultado en este caso específico. Un cambio menor en cualquiera de los dos podría alterar el resultado.
Kevin B
¿Qué quieres decir con "alterar el resultado"? Lo siento, estoy realmente
confuso
Por ejemplo, si en lugar de lanzar un error simplemente no hiciste nada, la promesa pasaría de ser rechazada a resolverse. Eso, por supuesto, alteraría el resultado, porque la promesa ahora es una promesa resuelta en lugar de una rechazada. (a menos, por supuesto, que ya se haya resuelto, en cuyo caso la captura no se habría ejecutado de todos modos)
Kevin B

Respuestas:

237

Entonces, básicamente estás preguntando cuál es la diferencia entre estos dos (dónde pestá una promesa creada a partir de un código anterior):

return p.then(...).catch(...);

y

return p.catch(...).then(...);

Existen diferencias cuando p resuelve o rechaza, pero si esas diferencias importan o no depende de lo que haga el código dentro de los controladores .then()o .catch().

Qué sucede cuando presuelve:

En el primer esquema, cuando se presuelve, .then()se llama al controlador. Si ese .then()controlador devuelve un valor u otra promesa que finalmente se resuelve, .catch()se omite el controlador. Pero, si el .then()manejador arroja o devuelve una promesa que finalmente se rechaza, entonces el .catch()manejador ejecutará tanto un rechazo en la promesa original pcomo un error que ocurra en el .then()manejador.

En el segundo esquema, cuando se presuelve, .then()se llama al controlador. Si ese .then()manejador lanza o devuelve una promesa que eventualmente rechaza, entonces el .catch()manejador no puede captar eso porque está antes que él en la cadena.

Entonces, esa es la diferencia n. ° 1. Si el .catch()controlador es DESPUÉS, también puede detectar errores dentro del .then()controlador.

Que pasa cuando prechaza:

Ahora, en el primer esquema, si la promesa se prechaza, .then()se omite el .catch()controlador y se llamará al controlador como era de esperar. Lo que haces en el .catch()controlador determina qué se devuelve como resultado final. Si solo devuelve un valor del .catch()controlador o devuelve una promesa que finalmente se resuelve, entonces la cadena de promesa cambia al estado resuelto porque "manejó" el error y regresó normalmente. Si arroja o devuelve una promesa rechazada en el .catch()controlador, la promesa devuelta permanece rechazada.

En el segundo esquema, si la promesa se prechaza, .catch()se llama al controlador. Si devuelve un valor normal o una promesa que finalmente se resuelve desde el .catch()controlador (por lo tanto, "maneja" el error), entonces la cadena de promesa cambia al estado resuelto y se llamará al .then()controlador posterior .catch().

Entonces esa es la diferencia # 2. Si el .catch()controlador es ANTES, entonces puede manejar el error y permitir .then()que se siga llamando al controlador.

Cuándo usar cuál:

Utilice el primer esquema si solo desea un .catch()controlador que pueda detectar errores en la promesa original po en el .then()controlador y un rechazo de pdebería omitir el .then()controlador.

Utilice el segundo esquema si desea poder detectar errores en la promesa original py tal vez (dependiendo de las condiciones), permitir que la cadena de promesa continúe como se resolvió, ejecutando así el .then()controlador.

La otra opcion

Hay otra opción para usar ambas devoluciones de llamada a las que puede pasar .then()como en:

 p.then(fn1, fn2)

Esto garantiza que solo uno de fn1o fn2será llamado. Si se presuelve, entonces fn1se llamará. Si prechaza, entonces fn2será llamado. Ningún cambio de resultado en fn1nunca puede hacer que fn2te llamen o viceversa. Por lo tanto, si desea estar absolutamente seguro de que solo se llama a uno de sus dos controladores, independientemente de lo que suceda en los mismos controladores, puede usar p.then(fn1, fn2).

jfriend00
fuente
17
La pregunta es específicamente sobre el orden de .then()y .catch(), que respondes. Además, da algunos consejos sobre cuándo usar qué orden, donde creo que es apropiado mencionar una tercera opción, es decir, pasar el controlador de éxito y error a .then () . En ese caso, se llamará a un controlador como máximo.
ArneHugo
7
@ArneHugo - Buena sugerencia. Yo añadí.
jfriend00
Entonces, durante el encadenamiento de promesas, ¿podemos escribir .entonces .capturar .capturar .entonces tipo de escenarios?
Kapil Raghuwanshi
@KapilRaghuwanshi, sí, puede usarlo para pasar un valor predeterminado en caso de falla. ie Promise.reject(new Error("F")).then(x => x).catch(e => {console.log(e); return [1]}).then(console.log)y Promise.resolve([2]).then(x => x).catch(e => [1]).then(console.log)
CervEd
1
@DmitryShvedov - Como supuse, esto está mal .then(this.setState({isModalOpen: false})). No está pasando una referencia de función a .then()para que el código en los parens se ejecute inmediatamente (antes de que se resuelva la promesa). Debería serlo .then(() => this.setState({isModalOpen: false})).
jfriend00
31

La respuesta de jfriend00 es excelente, pero pensé que sería una buena idea agregar el código sincrónico análogo.

return p.then(...).catch(...);

es similar al sincrónico:

try {
  iMightThrow() // like `p`
  then()
} catch (err) {
  handleCatch()
}

Si iMightThrow()no lanza, then()se llamará. Si lanza (o si then()lanza él mismo), entonces handleCatch()será llamado. Observe cómo el catchbloque no tiene control sobre si thense llama o no .

Por otra parte,

return p.catch(...).then(...);

es similar al sincrónico:

try {
  iMightThrow()
} catch (err) {
  handleCatch()
}

then()

En este caso, si iMightThrow()no lanza, then()se ejecutará. Si arroja, entonces dependerá de handleCatch()decidir si then()se llama, porque si se handleCatch()vuelve a lanzar, then()no se llamará, ya que la excepción se lanzará al llamador inmediatamente. Si handleCatch()puede manejar el problema con elegancia, entonces then()será llamado.

Akivajgordon
fuente
esta es una buena explicación, pero podría envolver al huérfano then()en unfinally{...}
tyskr
2
@ 82Tuskers, ¿estás seguro? Si pongo then()en finally{...}, ¿no forma incorrecta puede llamar incluso si handleCatch()lanza? Tenga en cuenta que mi objetivo era mostrar código síncrono análoga, no es sugerir diferentes formas de manejo de excepciones
akivajgordon
Entonces, si queremos manejar todos los casos pero aún encadenar .then () sería mejor usar .then (hacer algo) .catch (log err y actualizar estado). Then (hacer otra cosa) .catch (log err) ¿Dónde intentamos atrapar en cada punto pero también continuamos ejecutando los stmnts?
Anna