Saltar al contenido

Comandos que no devuelven nada

Un comando que solo hace algo tiene un problema propio: el frontend no sabe qué ha cambiado. Los tres patrones para resolverlo, y por qué devolver el estado nuevo casi siempre gana a no devolver nada.

El problema

Rust
#[tauri::command]async fn marcar_resuelto(id: ProblemaId) -> Result<(), ErrorPayload> {    logica::marcar(id).await?;    Ok(())}

El comando funciona. Y el frontend, después de esperarlo, tiene un problema: su lista de problemas sigue diciendo que ese está sin resolver, porque nada la ha tocado. Además de la marca, probablemente hayan cambiado un contador, una fecha de modificación y quizá el orden.

Las tres salidas:

El patrón por defecto

Devolver lo que cambió, con el tipo de la pantalla del nivel 15.3:

Rust
#[tauri::command]async fn marcar_resuelto(id: ProblemaId) -> Result<ProblemaResumen, ErrorPayload> {    let p = logica::marcar(id).await?;    Ok(ProblemaResumen::de(p))       // lo que la lista necesita para repintarse}
JS
const actualizado = await commands.marcarResuelto(id);lista.reemplazar(actualizado);      // sin recargar nada

Un viaje, sin recarga, sin instante inconsistente. Y una ventaja que no se ve hasta que aparece un fallo: si el comando falla, el frontend no ha cambiado nada, así que no hay que deshacer.

Cuándo el evento es obligatorio

En cuanto el cambio le importa a alguien más que a quien lo pidió. Los tres casos típicos:

  • Hay otra ventana abierta que muestra lo mismo. Nivel 12.5.
  • Hay otra vista en la misma ventana que depende del mismo dato: un contador en la barra lateral, un resumen en la cabecera.
  • El cambio puede venir de otro sitio: una sincronización de fondo, un fichero que cambió en disco, otra instancia. Ahí el evento no es una optimización, es la única forma.

Y el detalle de implementación que evita un parpadeo: quien llamó ya tiene la respuesta, así que si además recibe el evento va a repintar dos veces. Se resuelve emitiendo el evento con la etiqueta de la ventana origen dentro, para que esa se lo salte, o dirigiendo el evento a las demás. Nivel 20.2.

La actualización optimista

Con el patrón 2 hay una tercera posibilidad que la interfaz agradece mucho: cambiar la interfaz antes de que el comando conteste y corregir si falla.

JS
lista.marcar(id, true);                       // la interfaz responde al instantetry {  const real = await commands.marcarResuelto(id);  lista.reemplazar(real);                     // la verdad, cuando llega} catch (e) {  lista.marcar(id, false);                    // deshacer: el core dijo que no  avisar(e.message);}

Eso convierte una interacción que tenía la latencia del puente en una instantánea. Y tiene un requisito que no se puede saltar: el comando debe devolver el estado real, para poder sustituir la suposición por el hecho. Con el patrón 1 no se puede hacer bien, porque no hay con qué corregir.