Reanudar desde la caché cuando la fuente muere

Empate async

Requisito

El feed de pedidos en vivo entrega tres actualizaciones y entonces la conexión muere. El panel sigue necesitando sus primeras seis filas: conserva todo lo que el feed en vivo alcanzó a entregar y continúa después desde la instantánea cacheada de anoche, marcando esas filas con (from cache). El fallo se inyecta de forma determinista en el código; las dos versiones deben imprimir las líneas que aparecen bajo Salida esperada.

Salida esperada
ORD-7011 packed
ORD-7012 shipped
ORD-7013 packed
ORD-7014 picking (from cache)
ORD-7015 picking (from cache)
ORD-7016 received (from cache)

Lado a lado

RxDart

FxDart

Por qué difieren

Este es el canal de errores en su mejor momento. El punto de recuperación es de stream completo — «cuando esta fuente muera, cambia a aquella otra para el resto de la secuencia» — que es exactamente la forma que un canal de errores push modela. onErrorResumeNext dice el requisito entero en un solo operador: los valores pasan intactos, y el primer error conmuta la suscripción al stream de recuperación en pleno vuelo, con las tres actualizaciones entregadas ya a salvo.

El lado pull no tiene ningún operador que conserve los valores de una fuente que después lanza — un pipeline pull hace aflorar el error en el punto del pull, y un toList fallido descartaría lo que vino antes. Así que la frontera se escribe a mano: un bucle await for recoge las filas en vivo, el try/catch pone nombre al fallo, y concat + map + take empalman la cola cacheada. Dos o tres líneas honestas más — la misma recuperación, menos la palabra del vocabulario.

Un empate, inclinado hacia RxDart en elegancia aquí. Ambos lados se detienen en seis filas, y ninguno lee la caché más allá de lo que la página necesita: take cancela la suscripción en un lado y simplemente deja de tirar en el otro.

Benchmark

Apple M1 Max, 32 GB de RAM · Dart 3.12.2 (compilado AOT) · 2026-08-18

Caso async: la escala principal es N = 10,000, no 1,000,000. Cada elemento cuesta una vuelta del event loop en ambos lados, así que un millón de awaits reales mediría el event loop de Dart durante minutos — no el pipeline. Los retardos son de longitud cero y se conserva el límite de concurrencia del ejemplo; lo que comparan las barras es la maquinaria del pipeline.

N = 100

Tiempo Empate

RxDart 28 µs
FxDart 26 µs

Memoria pico Empate

RxDart 16.5 MB
FxDart 16.6 MB

N = 10,000

Tiempo Empate

RxDart 1.80 ms
FxDart 1.97 ms

Memoria pico Empate

RxDart 23.2 MB
FxDart 23.7 MB

Las barras son medianas de iteraciones cronometradas repetidas en procesos nuevos por lado (los N pequeños se agrupan por resolución del temporizador). Dos lados a menos del 5% entre sí — o a menos de 0.6 ms, una diferencia que nadie puede percibir — cuentan como empate; las carreras relativas ajustadas se vuelven a medir hasta 5 veces. En una app, cualquier cosa por debajo de unos pocos milisegundos es invisible para el usuario, gane la barra que gane. La memoria es el RSS pico del proceso. La VM de Dart y el dataset son idénticos en ambos lados, así que la diferencia entre las dos barras es lo que retiene el pipeline en sí.