import deepEqual from 'fast-deep-equal' type PromiseCache = { promise: Promise args: Args error?: any response?: Response } type PromiseFn = (...args: Args) => Promise const globalCache: PromiseCache[] = [] function handleAsset( fn: PromiseFn, cache: PromiseCache[], args: Args, lifespan = 0, preload = false ) { for (const entry of cache) { // Find a match if (deepEqual(args, entry.args)) { // If we're pre-loading and the element is present, just return if (preload) return // If an error occurred, throw if (entry.error) throw entry.error // If a response was successful, return if (entry.response) return entry.response // If the promise is still unresolved, throw throw entry.promise } } // The request is new or has changed. const entry: PromiseCache = { args, promise: // Make the promise request. fn(...args) // Response can't be undefined or else the loop above wouldn't be able to return it // This is for promises that do not return results (delays for instance) .then((response) => (entry.response = (response ?? true) as Response)) .catch((e) => (entry.error = e ?? 'unknown error')) .then(() => { if (lifespan > 0) { setTimeout(() => { const index = cache.indexOf(entry) if (index !== -1) cache.splice(index, 1) }, lifespan) } }), } cache.push(entry) if (!preload) throw entry.promise } function clear(cache: PromiseCache[], ...args: Args) { if (args === undefined || args.length === 0) cache.splice(0, cache.length) else { const entry = cache.find((entry) => deepEqual(args, entry.args)) if (entry) { const index = cache.indexOf(entry) if (index !== -1) cache.splice(index, 1) } } } function createAsset(fn: PromiseFn, lifespan = 0) { const cache: PromiseCache[] = [] return { /** * @throws Suspense Promise if asset is not yet ready * @throws Error if the promise rejected for some reason */ read: (...args: Args): Response => handleAsset(fn, cache, args, lifespan) as Response, preload: (...args: Args): void => void handleAsset(fn, cache, args, lifespan, true), clear: (...args: Args) => clear(cache, ...args), peek: (...args: Args): void | Response => cache.find((entry) => deepEqual(args, entry.args))?.response, } } function useAsset(fn: PromiseFn, ...args: Args): Response { return handleAsset(fn, globalCache as PromiseCache[], args, useAsset.lifespan) as Response } useAsset.lifespan = 0 useAsset.clear = (...args: Args) => clear(globalCache, ...args) useAsset.preload = (fn: PromiseFn, ...args: Args) => void handleAsset(fn, globalCache as PromiseCache[], args, useAsset.lifespan, true) useAsset.peek = (...args: Args) => globalCache.find((entry) => deepEqual(args, entry.args))?.response as Response export { createAsset, useAsset }