El caso es de una función de nueve líneas. Un memoize casero para que una pantalla de listados no pidiera dos veces lo mismo en el mismo render. La clave de caché, lo de siempre: JSON.stringify(args) y a otra cosa. Lo escribes sin pensar porque lo has escrito veinte veces.
El contador de aciertos marcaba cero. Cero, con la misma pantalla pidiendo lo mismo dos veces seguidas.
Dos objetos iguales, dos cadenas distintas
Las dos llamadas pasaban el mismo objeto de opciones, pero no construido igual. Una salía tal cual del estado del filtro, con el orden en que se declararon los campos. La otra se montaba con un spread y dos propiedades añadidas al final. Mismo contenido, distinto orden de inserción. Y JSON.stringify es fiel a ese orden: {pagina, estado} y {estado, pagina} son dos textos diferentes. La caché estaba haciendo exactamente lo que le pedí.
Lo interesante vino al arreglarlo. Le pedí al modelo que corre en la pestaña una serialización estable de la clave, y además de ordenar las claves de forma recursiva me preguntó qué quería hacer con los undefined. Ahí estaba el fallo que no había visto: stringify se come las propiedades con valor undefined. {estado: «abierto», cliente: undefined} y {estado: «abierto»} generan la misma clave. Una consulta de un cliente concreto y otra de todos los clientes compartían resultado. No era solo que fallara cuando debía acertar: es que acertaba cuando debía fallar, y eso no sale en ningún contador.
La lista de cosas que una clave de caché honesta tiene que decidir salió de esa conversación:
- Ordenar las claves antes de serializar, y hacia dentro, no solo en el primer nivel.
- Marcar el undefined explícitamente en vez de dejar que desaparezca.
- Fechas, Map y Set: stringify las aplana o las deja vacías. O las conviertes a mano o las prohíbes en la clave.
- NaN y -0, que se serializan como null y 0.
- Referencias cíclicas, que directamente lanzan.
Quince líneas y seis tests después, el contador subió. Pero lo que arreglé de verdad fue el acierto falso.
Por qué esto se escribe en local
Para que alguien te ayude con esta función tiene que ver los objetos de verdad. No un foo y un bar: los nombres reales de tus campos, los filtros que existen en tu panel, el identificador de cliente, qué se pasa opcional y qué no. Eso es tu esquema, y pegado en un chat remoto es un volcado bastante fiel de cómo funciona tu producto por dentro. Aquí el prompt, el objeto de ejemplo y el código no salen de la pestaña. No hay nada que recordar no mandar.
El bug no estaba en la caché. Estaba en haber dado por buena una clave que, siendo igual el contenido, podía no ser igual.
Si tienes un memoize de esos a medio escribir, ábrelo en Elffuss Code: el modelo corre en tu navegador, sobre tu GPU, y ni tu código ni tus objetos de ejemplo suben a ningún sitio.
Abrir Code →