La mayor parte del código frontend trata el trabajo asíncrono como una única forma. Inicias una Promise, esperas a que se resuelva, vuelcas el resultado en el estado local y dejas que el framework reconcilie la diferencia. Este patrón es seductor porque funciona en todas partes: una llamada REST, el envío de un formulario, un mensaje de WebSocket. Todos aterrizan en el mismo useEffect o manejador de eventos, todos pasan por un setState y todos parecen la misma fontanería asíncrona. Esa uniformidad es una trampa. En una aplicación real, no todo el trabajo asíncrono es igual. Pretender que lo es convierte a tus componentes de UI en arquitectos de datos accidentales, remendados con hooks useEffect y cruzando los dedos.

Las operaciones asíncronas en realidad se dividen en tres especies diferentes. Cada una tiene una relación distinta con el tiempo, el almacenamiento en caché y la propiedad. Aprender a distinguirlas es lo que mantiene un frontend rápido, correcto y cuerdo.

Queries: Hechos con una dirección

Una query no es solo un fetch. Es una petición de un hecho identificable. Estás pidiendo /user/123, no "algunos datos de usuario". Esa distinción es importante porque la identidad es lo que hace posible el almacenamiento en caché. Si dos componentes en la misma pantalla necesitan el mismo registro de usuario, deberían compartir una única respuesta. Cuando cada componente mantiene su propia copia local en useState, fragmentas tu verdad. El avatar en el encabezado y el nombre en la barra lateral se desincronizan porque se obtuvieron en momentos diferentes, o porque uno falló mientras el otro tuvo éxito.

Piensa en una query como un recurso, no como una acción. Tiene una clave de caché, una política de frescura y un ciclo de vida que sobrevive a cualquier componente individual. Una capa de queries bien construida entiende que leer /projects?page=2 es diferente a leer /projects?page=3. Cada URL y conjunto de parámetros forma una dirección, y los datos en esa dirección pueden estar obsoletos, frescos o ausentes. La UI no debería encargarse de esta contabilidad. Debería pedir a la capa de datos user:123 y recibir una instantánea. El hecho de que esa instantánea provenga del servidor hace dos segundos o de una caché hace dos milisegundos no es asunto del componente.

Las consecuencias prácticas son inmediatas. Cuando tratas cada lectura como un fetch imperativo dentro de un componente, pierdes la deduplicación. Pierdes la actualización en segundo plano. Pierdes la capacidad de mostrar datos en caché instantáneamente mientras los validas en segundo plano. Una query merece un hogar fuera de tu árbol de UI.

Mutations: Cambiando el mundo

Si las queries preguntan sobre el mundo, las mutations lo cambian. La solicitud de red real —el POST, PUT o DELETE— suele ser la parte fácil. La parte difícil es todo lo que sucede después de que el servidor dice "OK".

Supongamos que un usuario actualiza su nombre de usuario. La mutation en sí es una única solicitud. Pero el radio de impacto está en todas partes. La página de perfil mantiene el nombre antiguo. La barra de navegación muestra el nombre antiguo. El historial de comentarios podría hacer referencia a él. Si tu código de mutation no hace nada más que alternar una bandera isLoading local y luego actualizar una pieza de estado, tu aplicación ahora le está mintiendo a sí misma. Algunos rincones de la UI fingen que el cambio ocurrió. Otros no tienen idea de que algo haya cambiado.

Una mutation debe declarar su impacto en el grafo de datos. Debe decirle al sistema qué queries son ahora inválidas, qué claves de caché deben volver a consultarse y qué relaciones han cambiado. Esto es fundamentalmente diferente de una query. Una query es de solo lectura y compartible. Una mutation se centra en la escritura y es destructiva para las cachés existentes. Colapsarlas en la misma abstracción significa que los desarrolladores terminan llamando manualmente a refetch() dentro de componentes aleatorios o, peor aún, esparciendo hooks useEffect por todo el árbol para "sincronizar" el estado local y alinearlo de nuevo con el servidor.

El modelo de propiedad también es diferente. Las queries suelen ser propiedad de la caché. Una mutation es propiedad de la acción del usuario que la desencadenó. Tiene un estado pendiente, un estado de error y, potencialmente, un valor optimista que necesita ser revertido