From d278c75688cc83a9f220f745356e4dc08f625782 Mon Sep 17 00:00:00 2001 From: Fran Date: Sat, 14 Feb 2026 19:17:50 -0300 Subject: [PATCH] fix race condition in isfollowing --- src/lib/components/BotonSeguir.svelte | 17 ++-- src/lib/hooks/logout.ts | 2 +- src/lib/stores/cacheSeguidos.js | 134 +++++++++++++++++++++++++ src/lib/stores/cacheSeguidos.svelte.js | 105 ------------------- 4 files changed, 142 insertions(+), 116 deletions(-) create mode 100644 src/lib/stores/cacheSeguidos.js delete mode 100644 src/lib/stores/cacheSeguidos.svelte.js diff --git a/src/lib/components/BotonSeguir.svelte b/src/lib/components/BotonSeguir.svelte index 2a083b3..e1eee23 100644 --- a/src/lib/components/BotonSeguir.svelte +++ b/src/lib/components/BotonSeguir.svelte @@ -11,7 +11,7 @@ import { seguirUsuario } from '@/hooks/seguirUsuario'; import type { Post } from '../../types'; import CardError from './CardError.svelte'; - import { cacheSeguidos } from '@/stores/cacheSeguidos.svelte'; + import { cacheSeguidos } from '@/stores/cacheSeguidos.js'; let { post, @@ -41,16 +41,13 @@ }); async function cargarSeguido() { - let a = cacheSeguidos.get(post.authorId); - if (a === undefined) { - const seguidoStatus = await esSeguido(post as Post); - if (seguidoStatus) { - cacheSeguidos.set(post.authorId, seguidoStatus.isFollowing || false); - seguido = seguidoStatus.isFollowing || false; + seguido = await cacheSeguidos.getOrFetch( + post.authorId, + async () => { + const seguidoStatus = await esSeguido(post as Post); + return seguidoStatus?.isFollowing || false; } - return; - } - seguido = a; + ); } let mensajeError: string | null = $state(null); diff --git a/src/lib/hooks/logout.ts b/src/lib/hooks/logout.ts index c21c50c..16e569c 100644 --- a/src/lib/hooks/logout.ts +++ b/src/lib/hooks/logout.ts @@ -1,5 +1,5 @@ import { goto } from '$app/navigation'; -import { cacheSeguidos } from '@/stores/cacheSeguidos.svelte'; +import { cacheSeguidos } from '@/stores/cacheSeguidos'; import { apiBase } from '@/stores/url'; import { sesionStore } from '@/stores/usuario'; import { get } from 'svelte/store'; diff --git a/src/lib/stores/cacheSeguidos.js b/src/lib/stores/cacheSeguidos.js new file mode 100644 index 0000000..907936f --- /dev/null +++ b/src/lib/stores/cacheSeguidos.js @@ -0,0 +1,134 @@ +import { browser } from '$app/environment'; +import { writable } from 'svelte/store'; + +class FollowCache { + constructor() { + if (browser) { + this.loadFromStorage(); + } + } + + /** @type {Map>} */ + #cache = new Map(); + + /** @type {import('svelte/store').Writable>} */ + store = writable(new Map()); + + /** @param {string} userId */ + get(userId) { + const value = this.#cache.get(userId); + return value instanceof Promise ? undefined : value; + } + + /** @param {string} userId */ + has(userId) { + return this.#cache.has(userId); + } + + /** + * @param {string} userId + * @param {() => Promise} fetchFn + */ + async getOrFetch(userId, fetchFn) { + const existing = this.#cache.get(userId); + + if (existing !== undefined) { + if (existing instanceof Promise) { + return existing; + } + return existing; + } + + const promise = fetchFn() + .then((result) => { + this.#setFinal(userId, result); + return result; + }) + .catch((err) => { + this.#cache.delete(userId); + this.#updateStore(); + throw err; + }); + + this.#cache.set(userId, promise); + this.#updateStore(); + + return promise; + } + + /** + * @param {string} userId + * @param {boolean} isFollowed + */ + set(userId, isFollowed) { + this.#setFinal(userId, isFollowed); + } + + /** + * @param {string} userId + * @param {boolean} value + */ + #setFinal(userId, value) { + this.#cache.set(userId, value); + this.#updateStore(); + this.saveToStorage(); + + if (browser) { + window.dispatchEvent( + new CustomEvent('followCacheUpdated', { + detail: { userId, isFollowed: value } + }) + ); + } + } + + #updateStore() { + const filtered = Array.from(this.#cache.entries()) + .filter(([_, v]) => typeof v === 'boolean'); + + this.store.set( + /** @type {Map} */ + (new Map(filtered)) + ); + } + + /** @param {string} userId */ + delete(userId) { + this.#cache.delete(userId); + this.#updateStore(); + this.saveToStorage(); + } + + clear() { + this.#cache.clear(); + this.store.set(new Map()); + this.saveToStorage(); + } + + saveToStorage() { + if (!browser) return; + const filtered = Array.from(this.#cache.entries()) + .filter(([_, v]) => typeof v === 'boolean'); + + const data = Object.fromEntries(filtered); + sessionStorage.setItem('follow-cache', JSON.stringify(data)); + } + + loadFromStorage() { + if (!browser) return; + + try { + const stored = sessionStorage.getItem('follow-cache'); + if (!stored) return; + const data = JSON.parse(stored); + + this.#cache = new Map(Object.entries(data)); + this.#updateStore(); + + } catch (err) { + console.error('Error cargando follow-cache:', err); + } + } +} + +export const cacheSeguidos = new FollowCache(); \ No newline at end of file diff --git a/src/lib/stores/cacheSeguidos.svelte.js b/src/lib/stores/cacheSeguidos.svelte.js deleted file mode 100644 index 279429a..0000000 --- a/src/lib/stores/cacheSeguidos.svelte.js +++ /dev/null @@ -1,105 +0,0 @@ -import { browser } from '$app/environment'; -import { writable } from 'svelte/store'; - -class FollowCache { - constructor() { - if (browser) { - this.loadFromStorage(); - } - } - - /** @type {Map} */ - #cache = new Map(); - - /** @type {import('svelte/store').Writable>} */ - store = writable(this.#cache); - - /** - * @param {string} userId - * @returns {boolean | undefined} - */ - get(userId) { - return this.#cache.get(userId); - } - - /** - * @param {string} userId - * @param {boolean} isFollowed - */ - set(userId, isFollowed) { - this.#cache.set(userId, isFollowed); - this.store.set(this.#cache); - this.saveToStorage(); - - if (browser) { - window.dispatchEvent( - new CustomEvent('followCacheUpdated', { - detail: { userId, isFollowed } - }) - ); - } - } - - /** - * @param {string} userId - * @returns {boolean} - */ - has(userId) { - return this.#cache.has(userId); - } - - /** - * @param {string} userId - */ - delete(userId) { - this.#cache.delete(userId); - this.store.set(this.#cache); - this.saveToStorage(); - - if (browser) { - window.dispatchEvent( - new CustomEvent('followCacheUpdated', { - detail: { userId, isFollowed: false } - }) - ); - } - } - - clear() { - this.#cache.clear(); - this.store.set(this.#cache); - this.saveToStorage(); - - if (browser) { - window.dispatchEvent( - new CustomEvent('followCacheUpdated', { - detail: { clearAll: true } - }) - ); - } - } - - saveToStorage() { - if (browser) { - const data = Object.fromEntries(this.#cache); - sessionStorage.setItem('follow-cache', JSON.stringify(data)); - } - } - - loadFromStorage() { - if (browser) { - try { - const stored = sessionStorage.getItem('follow-cache'); - if (stored) { - const data = JSON.parse(stored); - this.#cache = new Map(Object.entries(data)); - this.store.set(this.#cache); - } - } catch (error) { - console.error('Error cargando desde sesion:', error); - } - } - } -} - -export const cacheSeguidos = new FollowCache();