introduccion-programacion-reactiva

Introducción a la programación reactiva con Rx

  • 6 min

La programación reactiva es un paradigma orientado a flujos de datos y a la propagación de cambios. En lugar de preguntar continuamente si ha ocurrido algo, describimos cómo reaccionar cuando llega un nuevo valor.

El mundo real no siempre es secuencial. Muchos sistemas reciben información de forma asíncrona. El usuario hace clic cuando quiere, el servidor responde cuando puede, y el GPS nos envía coordenadas cuando le da la gana.

Intentar modelar esto con bucles while o con el infierno de los callbacks es doloroso. Ahí encaja la Programación Reactiva.

En lugar de pedir datos (Pull), nos sentamos a esperar a que los datos vengan a nosotros (Push), y reaccionamos a ellos.

La metáfora de Excel

La mejor forma de entender la reactividad es pensar en una hoja de cálculo.

  • Enfoque imperativo: Tienes la celda A1 con un 10 y la B1 con un 20. En C# harías: var c1 = a1 + b1;. c1 vale 30. Si ahora cambias a1 = 50, c1 sigue valiendo 30. Tienes que volver a ejecutar la línea de suma para actualizarlo.
  • Enfoque reactivo (Excel): En la celda C1 escribes =SUM(A1, B1). En el momento en que cambias A1, C1 se actualiza automáticamente.

En programación reactiva, las variables no son cajas estáticas, son flujos de valores en el tiempo. C1 está “suscrita” a los cambios de A1 y B1.

Qué es la programación reactiva

Definición formal:

La programación reactiva modela valores que cambian y propaga esos cambios a quienes dependen de ellos.

Con bibliotecas como Rx podemos tratar un clic, un temporizador o una respuesta HTTP como un flujo de eventos. No todo flujo reactivo tiene que ser asíncrono, aunque ese sea uno de sus usos más frecuentes.

Y lo mejor de todo: podemos aplicarles operadores funcionales. Sí, los mismos que vimos en el artículo anterior (Map, Filter, Scan).

Los componentes de Rx (Reactive Extensions)

La implementación más famosa de este paradigma es la familia Reactive Extensions (Rx), disponible en casi todos los lenguajes (RxJS, Rx.NET, RxJava/Kotlin).

Rx combina ideas de dos interfaces conocidas:

  • Observer: el productor envía valores al consumidor.
  • Iterator: sirve como contraste; en lugar de pedir el siguiente elemento, el observable lo entrega cuando está disponible.

En Rx, tenemos tres actores principales:

Es la fuente de datos. Es el “grifo” que emite eventos a lo largo del tiempo. Puede emitir tres cosas:

  • Un Valor (Next).
  • Un Error (Error).
  • Una señal de Completado (Completed).

Es quien escucha. Define qué hacer cuando llega un dato, cuando hay un error o cuando el flujo termina.

Son funciones puras que transforman el flujo antes de que llegue al suscriptor. Ahí está lo interesante.

Ejemplo práctico: el buscador predictivo

Vamos a ver el caso de uso por excelencia: La barra de búsqueda. Queremos que, cuando el usuario escriba, busquemos en el servidor. Pero con condiciones:

  • No buscar si el texto tiene menos de tres letras.
  • Esperar a que el usuario deje de escribir para no lanzar una petición por tecla.
  • No repetir dos búsquedas consecutivas con el mismo texto.
  • Ignorar el resultado anterior si comienza una búsqueda nueva.

La versión imperativa con callbacks y timers

(Es pseudocódigo para ilustrar la gestión manual; no pretende compilar.)

// ❌ CÓDIGO IMPERATIVO "SPAGHETTI"
private Timer _timer;
private string _ultimaBusqueda;

void OnTextChanged(string texto) {
    if (texto.Length < 3) return;

    // Gestión manual del timer para el debounce
    if (_timer != null) _timer.Stop();
    _timer = new Timer(500);
    _timer.Elapsed += (s, e) => {
        if (texto == _ultimaBusqueda) return;
        _ultimaBusqueda = texto;

        LlamarApi(texto, (resultado) => {
             // Cuidado con condiciones de carrera aquí...
             // ¿Y si llega otra respuesta antes? ¡Caos!
             Mostrar(resultado);
        });
    };
    _timer.Start();
}
Copied!

La versión reactiva con Rx.NET

Usando System.Reactive y LINQ.

// ✅ CÓDIGO REACTIVO
// Convertimos el evento de UI en un Observable (un flujo de strings)
var busquedas = Observable.FromEventPattern<TextChangedEventArgs>(txtBuscar, "TextChanged")
    .Select(evt => ((TextBox)evt.Sender).Text) // Extraemos el texto
    .Throttle(TimeSpan.FromMilliseconds(500))  // Esperamos 500ms de silencio (Debounce)
    .Where(texto => texto.Length >= 3)         // Filtramos textos cortos
    .DistinctUntilChanged()                    // Ignoramos si es igual al anterior
    .Select(texto => LlamarApi(texto))         // Proyectamos a la llamada API
    .Switch();                                 // Si llega una nueva, cancela la anterior

// Nos suscribimos al resultado final
busquedas.Subscribe(resultado => {
    Console.WriteLine($"Resultados recibidos: {resultado.Count}");
});
Copied!

Fíjate en la diferencia. Hemos descrito el flujo lógico de los datos. No hay variables temporales, ni gestión manual de timers, ni anidamiento de callbacks.

El operador .Switch() se desuscribe del observable anterior cuando llega uno nuevo. La operación subyacente solo se cancela de verdad si el observable implementa esa cancelación; en cualquier caso, su resultado deja de entregarse al suscriptor.

Observables hot y cold

Este es un concepto técnico que suele confundir al principio.

  • Cold observables (fríos): son como reproducir una película bajo demanda. La secuencia comienza al suscribirte y cada suscriptor obtiene su propia ejecución.

  • Ejemplo: una llamada HTTP o la lectura de un archivo.

  • Hot observables (calientes): son como la radio o un partido en directo. La emisión ocurre independientemente de tu suscripción y, si llegas tarde, te pierdes el principio.

  • Ejemplo: los clics del ratón o una cotización en tiempo real.

¿Cuándo usar Programación Reactiva?

No uses Rx para sumar dos números. Úsalo cuando tengas complejidad asíncrona:

  • Eventos de UI complejos: drag and drop, combinaciones de teclas o validaciones en tiempo real.
  • WebSockets y SignalR: datos que llegan en tiempo real desde el servidor.
  • Composición de operaciones asíncronas: combinar, transformar o recuperar flujos con operadores como Merge, Zip y Catch.

La programación reactiva requiere un cambio de mentalidad. Dejas de pensar en “pasos” y empiezas a pensar en “tuberías” por las que fluyen los datos.

Al principio cuesta. Mirarás un diagrama de canicas (Marble Diagram) y no entenderás nada. Pero una vez dominas operadores como FlatMap (o SelectMany en C#), te darás cuenta de que el código imperativo tradicional te parece prehistórico para gestionar eventos.