Búsqueda en vivo sobre un stream de pulsaciones
Requisito
Un cuadro de búsqueda emite cada pulsación de tecla como un
Stream de Dart —alguien escribiendo hacia
darts, con algunos valores repetidos por la repetición
automática del teclado (secuencia fija en el código de abajo).
Conviértelo en búsquedas de backend: descarta las consultas de menos
de dos caracteres, no busques nunca dos veces la misma consulta,
detente después de cuatro consultas buscadas e imprime cada consulta
con su número de resultados y el mejor resultado —además de cuántas
llamadas al backend se hicieron realmente.
Con fxStream el stream de pulsaciones se convierte en un
pipeline y cada regla se convierte en un operador: filter
para el mínimo de longitud, uniq para las repeticiones,
take(4) para el presupuesto, y después map
realiza la búsqueda. Como take está antes del paso de
búsqueda y la cadena está basada en pull, se producen exactamente
cuatro llamadas al backend y la cola del stream no se consume nunca.
Salida esperada
live search over the keystroke stream: 'da' -> 5 hits (top: dart language tour) 'dar' -> 5 hits (top: dart language tour) 'dart' -> 5 hits (top: dart language tour) 'darts' -> 1 hit (top: darts scoring rules) backend searches: 4
Lado a lado
Dart nativo
FxDart
Por qué difieren
El bucle nativo await for es compacto —pero fíjate en
dónde han acabado las reglas: el mínimo de longitud y la
deduplicación comparten una única expresión continue
(q.length < 2 || !seen.add(q), que cuela una mutación
dentro de una condición), y el presupuesto es una comprobación de
contador con un break. Tres políticas comprimidas en dos
cláusulas de guarda; añadir una cuarta implica desenredarlas. El
pipeline gasta un operador con nombre por regla, en el orden en que se
aplican, y esa misma cadena aceptaría sin cambios el stream de cambios
de texto de un widget real. Una advertencia honesta: el
debounce de fxdart es una utilidad para llamadas a
funciones, no un operador de stream —silenciar por tiempo un stream
parlanchín es una herramienta distinta de las cuatro reglas que se ven
aquí.
Benchmark
Caso async: la escala principal es N = 100,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
Memoria pico Empate
N = 10,000
Tiempo Gana nativo
Memoria pico Empate
N = 100,000
Tiempo Gana nativo
Memoria pico Empate
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í.