Rendirse tras tres fallos

Gana FxDart async

Requisito

Un feed de diez sondas de salud corre en orden; las sondas 2, 5, 7, 8 y 9 lanzan. Detén la ejecución en el momento en que se vea el tercer fallo (incluyéndolo), y luego imprime tres conteos: processed — sondas que entraron al pipeline antes del corte; failures — cuántas de esas lanzaron; y probes run — cuerpos de sonda realmente ejecutados, contabilizados por un contador de efecto colateral dentro de la propia sonda. Las sondas posteriores no deben ejecutarse jamás, así que el conteo de ejecuciones tiene que coincidir con el de procesadas. El calendario está en el código; las dos versiones deben imprimir las líneas que aparecen bajo Salida esperada.

Salida esperada
processed: 7
failures: 3
probes run: 7

Lado a lado

RxDart

FxDart

Por qué difieren

El núcleo de conteo es el mismo en ambos lados — scan pliega un estado acumulado (done, fails), y un operador take-inclusivo corta el pipeline en el tercer fallo (takeUntilInclusive(fails == 3) en un lado, takeWhileInclusive(fails < 3) en el otro). Ambos además detienen el trabajo de verdad: probes run: 7 demuestra que cancelar la suscripción y dejar de tirar son frenos igual de efectivos.

La diferencia es lo que cada lado tuvo que hacer antes de que scan pudiera contar. Una sonda que lanza vive en el canal de errores del stream, donde scan no puede verla — y donde terminaría el stream en el fallo número uno. Así que el lado RxDart primero convierte cada sonda en un stream interno (Rx.fromCallable + onErrorReturn(false)) para contrabandear los fallos de vuelta al canal de datos como valores marcadores. El lado FxDart no necesita paso de conversión, porque no hay nada desde lo que convertir: un try/catch dentro de map hace del desenlace un bool justo donde ocurre, y el resto del pipeline es aritmética. Los mismos operadores, una frontera de modelo menos que cruzar.

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 818 µs
FxDart 413 µs

Memoria pico Empate

RxDart 16.6 MB
FxDart 17.0 MB

N = 10,000

Tiempo Gana FxDart

RxDart 65.5 ms
FxDart 37.6 ms

Memoria pico Empate

RxDart 22.2 MB
FxDart 22.6 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í.