diff --git a/package.json b/package.json index a59a09cd3..2612e716c 100644 --- a/package.json +++ b/package.json @@ -103,11 +103,11 @@ }, { "path": "packages/algoliasearch/dist/algoliasearch-lite.umd.js", - "maxSize": "4.6KB" + "maxSize": "4.7KB" }, { "path": "packages/recommend/dist/recommend.umd.js", - "maxSize": "4.4KB" + "maxSize": "4.5KB" } ] } diff --git a/packages/cache-browser-local-storage/src/createBrowserLocalStorageCache.ts b/packages/cache-browser-local-storage/src/createBrowserLocalStorageCache.ts index 1157bfc75..8fc292726 100644 --- a/packages/cache-browser-local-storage/src/createBrowserLocalStorageCache.ts +++ b/packages/cache-browser-local-storage/src/createBrowserLocalStorageCache.ts @@ -2,6 +2,22 @@ import { Cache, CacheEvents } from '@algolia/cache-common'; import { BrowserLocalStorageCacheItem, BrowserLocalStorageOptions } from '.'; +function yieldToMain(): Promise { + // eslint-disable-next-line no-undef + const g: any = typeof globalThis !== 'undefined' ? globalThis : undefined; + + if (g && g.scheduler && g.scheduler.yield) { + return g.scheduler.yield().catch((error: any) => { + // eslint-disable-next-line no-console + console.error('Failed to yield to main: ', error); + + return new Promise(resolve => setTimeout(resolve, 0)); + }); + } + + return new Promise(resolve => setTimeout(resolve, 0)); +} + export function createBrowserLocalStorageCache(options: BrowserLocalStorageOptions): Cache { const namespaceKey = `algoliasearch-client-js-${options.key}`; @@ -23,30 +39,24 @@ export function createBrowserLocalStorageCache(options: BrowserLocalStorageOptio getStorage().setItem(namespaceKey, JSON.stringify(namespace)); }; - const removeOutdatedCacheItems = () => { + const getFilteredNamespace = (): Record => { const timeToLive = options.timeToLive ? options.timeToLive * 1000 : null; const namespace = getNamespace(); + const currentTime = new Date().getTime(); - const filteredNamespaceWithoutOldFormattedCacheItems = Object.fromEntries( + return Object.fromEntries( Object.entries(namespace).filter(([, cacheItem]) => { - return cacheItem.timestamp !== undefined; - }) - ); - - setNamespace(filteredNamespaceWithoutOldFormattedCacheItems); + if (!cacheItem || cacheItem.timestamp === undefined) { + return false; + } - if (!timeToLive) return; + if (!timeToLive) { + return true; + } - const filteredNamespaceWithoutExpiredItems = Object.fromEntries( - Object.entries(filteredNamespaceWithoutOldFormattedCacheItems).filter(([, cacheItem]) => { - const currentTimestamp = new Date().getTime(); - const isExpired = cacheItem.timestamp + timeToLive < currentTimestamp; - - return !isExpired; + return cacheItem.timestamp + timeToLive >= currentTime; }) ); - - setNamespace(filteredNamespaceWithoutExpiredItems); }; return { @@ -57,25 +67,24 @@ export function createBrowserLocalStorageCache(options: BrowserLocalStorageOptio miss: () => Promise.resolve(), } ): Readonly> { - return Promise.resolve() - .then(() => { - removeOutdatedCacheItems(); - - const keyAsString = JSON.stringify(key); - - return getNamespace>()[keyAsString]; - }) - .then(value => { - return Promise.all([value ? value.value : defaultValue(), value !== undefined]); - }) - .then(([value, exists]) => { - return Promise.all([value, exists || events.miss(value)]); - }) - .then(([value]) => value); + return yieldToMain().then(() => { + const namespace = getFilteredNamespace(); + const keyAsString = JSON.stringify(key); + const cachedItem = namespace[keyAsString]; + + setNamespace(namespace); + + if (cachedItem) { + return cachedItem.value as TValue; + } + + // eslint-disable-next-line promise/no-nesting + return defaultValue().then((value: TValue) => events.miss(value).then(() => value)); + }); }, set(key: object | string, value: TValue): Readonly> { - return Promise.resolve().then(() => { + return yieldToMain().then(() => { const namespace = getNamespace(); // eslint-disable-next-line functional/immutable-data @@ -91,7 +100,7 @@ export function createBrowserLocalStorageCache(options: BrowserLocalStorageOptio }, delete(key: object | string): Readonly> { - return Promise.resolve().then(() => { + return yieldToMain().then(() => { const namespace = getNamespace(); // eslint-disable-next-line functional/immutable-data