import { ApiFn, DataOrModifiedFn, ModifierFn } from './types'; import { resourceCache } from './cache'; /** * Wrapper for an apiFunction without params. * It only takes the api function as an argument. * It returns a data reader with an optional modifier function. * * @param apiFn A typical api function that doesn't take any parameters. */ export function initializeDataReader( apiFn: ApiFn, ): DataOrModifiedFn; /** * Wrapper for an apiFunction with params. * It takes the api function and all its expected arguments. * Also returns a data reader with an optional modifier function. * * @param apiFn A typical api function with parameters. * @param parameters An arbitrary number of parameters. */ export function initializeDataReader( apiFn: ApiFn, ...parameters: ArgTypes ): DataOrModifiedFn; // implementation that covers the above overloads export function initializeDataReader( apiFn: ApiFn, ...parameters: ArgTypes ) { type AsyncStatus = 'init' | 'done' | 'error'; const apiFnCache = resourceCache(apiFn); const cachedResource = apiFnCache.get(...parameters); if (cachedResource) { return cachedResource; } let data: ResponseType; let status: AsyncStatus = 'init'; let error: unknown; const fetchingPromise = apiFn(...parameters) .then((result) => { data = result; status = 'done'; return result; }) .catch((err) => { error = err; status = 'error'; }); // the return type successfully satisfies DataOrModifiedFn function dataReaderFn(): ResponseType; function dataReaderFn(modifier: ModifierFn): M; function dataReaderFn(modifier?: ModifierFn) { if (status === 'init') { throw fetchingPromise; } else if (status === 'error') { throw error; } return typeof modifier === 'function' ? (modifier(data) as M) : (data as ResponseType); } apiFnCache.set(dataReaderFn, ...parameters); return dataReaderFn; }