Manejo de excepciones en callbackFlow con Kotlin | Por Florent Blot | Mar 2022
en nuestra aplicación de Android @jeeve, tratamos de usar Kotlin Flow para manejar las llamadas de Websockets para nuevos mensajes instantáneos.Utilizamos principalmente callbackFlow
Los generadores emiten y reciben datos. El constructor es fácil de usar y se mantiene vivo mientras escucha eventos de socket.
Pero es difícil para nosotros detectar excepciones inesperadas en este constructor.incluso uno try/catch
Las cláusulas sobre los recolectores de flujo son inútiles. Necesitamos manejar adecuadamente las excepciones inesperadas en este generador de procesos.
Supongamos que tenemos un proceso simple numbers()
recolectando números enteros de 0 a 2, pero al final, algo sucedió y arrojó una excepción inesperada, por check
Las funciones son las siguientes:
recopilados cuando aplicamos catch
operador para manejar las excepciones.Este operador captura solo excepciones aguas arriba (todas las excepciones operador desde arriba y no más bajo que eso).
Entonces usamos onEach
y launchIn
reemplazar collect
determinar solo catch
cogerá Cada Una excepción que ocurrió en la corriente ascendente.
Cuando se recopila, da el siguiente resultado:
Getting: 0
Getting: 1
Caught: java.lang.IllegalStateException: Check failed
Los números se recopilan bien e incluso se maneja la excepción lanzada.
¿Qué sucede si la excepción no se lanza en el generador sino en la devolución de llamada? Vamos a averiguar.
Necesitamos crear una devolución de llamada simple que nos dé el nuevo valor a emitir:
rediseñamos numbers()
Utilice esta nueva devolución de llamada:
Echemos un vistazo al código anterior:
- todavía usamos nuestro
callbackFlow
El generador devuelve unFlow
. - Esta
var callback
Nuestro nuevo oyente se define: llamar de vuelta. - nosotros cubrimos
onNextNumber
recibir un nuevo enteroi
Devolución de llamada desde arriba. - Esta
check
La función verifica que el entero recibido sea estrictamente menor que 2.Si no, lanzará unIllegalStateException
(Al igual que nuestro primer fragmento, simulará una excepción inesperada). - Si el proceso continúa, le enviamos el nuevo entero
trySend
. - y
awaitClose
Mantenga el tráfico en marcha. Es necesario mantener la transmisión abierta y limpiar los recursos cuando haya terminado, evitando pérdidas de memoria.
Finalmente, gracias repeat
función, enviamos 0 a 2 (nuestro índice it
a continuación) a través de nuestra devolución de llamada:
Ahora, usando el mismo colector que llamamos antes, obtenemos el siguiente resultado:
Getting: 0
Getting: 1
AndroidRuntime: FATAL EXCEPTION: main
Process: app, PID: 8141
java.lang.IllegalStateException: Check failed.
at MainActivity$numbers$1$1.onNextNumber(MainActivity.kt:40)
at MainActivity$onCreate$3.invokeSuspend(MainActivity.kt:32)
at BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
...
Espera… 😱 La aplicación falla inesperadamente a pesar de nuestra aplicación catch
operador.Además, terminamos en la misma situación. try/catch
Cláusula alrededor del flujo.excepción en Devolución de llamada no procesada.
Nuestro primer proceso detectó con éxito la excepción, mientras que el segundo proceso no. ¿Por qué?
Esta block
Pausa de Process Builder gama de productoreses una extensión Alcance de la rutinaEsta interfaz define un alcance para la nueva corrutina y, si ocurre una excepción, este alcance la manejará.Entonces, en nuestro primer fragmento, se arroja al alcance del generador de flujo y lo usa nuestro catch
operador.
Pero en nuestro segundo fragmento, se lanza la excepción en Oyente de devolución de llamada. El alcance ya no es el alcance de la rutina, por lo que el proceso no puede manejarlo. El manejo de tales excepciones es responsabilidad de la devolución de llamada.
vamos a girar repeat
función y try/catch
cláusula para verificar:
Recójalo ahora para confirmarlo, imprima el registro de devolución de llamada anterior:
Getting 0
Getting 1
Callback caught: java.lang.IllegalStateException: Check failed.
¿Cómo arrojamos excepciones en el ámbito correcto y activamos nuestro catch
¿Qué pasa con el operador?
ProducerScope también es enviar canalAsí que bajo el capó, numbers()
utilizar una canalPara propagar la excepción, tenemos dos opciones: quitar el ámbito de su rutina o cerrar su canal.
Se puede usar el rango de cancelación cancel
Es una extensión de CoroutineScope que también cancela su trabajo y todos sus hijos.lanza un Cancelar excepción Podemos especificar un motivo o incluir un mensaje con una excepción específica.
fun CoroutineScope.cancel(cause: CancellationException? = null)fun CoroutineScope.cancel(message: String, cause: Throwable? = null)
Por otro lado, tenemos close
Envía un «token de cierre» especial por el canal, con un motivo opcional. Podemos agregar una excepción específica como un motivo no nulo. Esto cerrará el canal, por lo que llamará a la finalización del proceso.
abstract fun close(cause: Throwable? = null): Boolean
Se comportan de la misma manera, por lo que el proceso se completará de manera anormal. catch
y onCompletion
sera llamado. Sin embargo, la eliminación del ámbito por un motivo se usa principalmente para proporcionar un motivo con fines de depuración.Si prefiere manejar un tipo específico de excepción, debe usar close
.
Entonces, con close
(o cancel
), tenemos la capacidad de propagar la excepción al alcance correcto.
Solo necesitamos rodear nuestro bloque de devolución de llamada con un try/catch
cláusula y cierre el canal con un motivo no nulo:
El proceso de recogernos ahora nos dará:
Getting: 0
Getting: 1
Caught: java.lang.IllegalStateException: Check failed
Al cerrar el canal de forma anormal, ahora podemos activar nuestro catch
operador y manejar cualquier error inesperado.
Al llamar a la función scoped o channel, ahora podemos activar nuestra catch
Operadores de diferentes gamas. Cada excepción de la devolución de llamada al constructor, incluso dentro del operador de transmisión, será manejada por catch
.
Si te ha resultado útil este artículo, ¡no dudes en aplaudir! 👏 Gracias por leer.