Separar éxitos de fallos

Gana FxDart async

Requisito

Siete pedidos de la importación 2026-08 pasan por una validación async que lanza para dos de ellos (una dirección de envío ausente, un SKU desconocido). Conserva ambos desenlaces: imprime una línea ok: por pedido válido, y luego el conteo de fallos. Los datos están en el código; las dos versiones deben imprimir las líneas que aparecen bajo Salida esperada.

Salida esperada
ok: order #1001
ok: order #1002
ok: order #1004
ok: order #1005
ok: order #1007
failed: 2

Lado a lado

RxDart

FxDart

Por qué difieren

Un stream tiene dos canales — datos y error — y el canal de errores tiene semántica de stream completo: una sola validación lanzada mataría el pipeline con cinco pedidos aún sin procesar. Así que el lado RxDart no puede simplemente asyncMap(validate); envuelve cada validación en su propio stream interno (Rx.fromCallable), captura en ese canal de errores interno con onErrorReturnWith, y re-codifica el fallo como un valor de datos antes de fusionar de vuelta. La recuperación funciona, pero es fontanería de canales: el error tuvo que abandonar la vía de datos solo para ser escoltado de regreso.

El lado FxDart nunca pone los fallos en un canal aparte. Un try/catch dentro de map convierte cada desenlace en un registro llano — (id, error?) — y a partir de ahí partition es una división por predicado corriente. Esta es la postura general del modelo pull ante los errores: son valores que fluyen por el mismo pipeline tipado que todo lo demás, así que mantener juntos éxitos y fallos no cuesta nada. Cuando los desenlaces son parte del resultado y no una interrupción, el modelo sin canal de errores privilegiado tiene menos que deshacer.

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 430 µs

Memoria pico Empate

RxDart 17.0 MB
FxDart 16.6 MB

N = 10,000

Tiempo Gana FxDart

RxDart 65.2 ms
FxDart 34.8 ms

Memoria pico Gana RxDart

RxDart 25.2 MB
FxDart 30.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í.