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
#[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:
#[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}const actualizado = await commands.marcarResuelto(id);lista.reemplazar(actualizado); // sin recargar nadaUn 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.
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.