Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(fetch): simple memory caching for remove duplicate/parallel requests #366

Merged
merged 2 commits into from
Nov 4, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 29 additions & 17 deletions packages/core/fetch/src/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,6 @@ alwatrRegisteredList.push({
version: '{{ALWATR_VERSION}}',
});

declare global {
// Patch typescript's global types
interface AbortController {
abort(reason?: string): void;
}
}

export type CacheStrategy = 'network_only' | 'network_first' | 'cache_only' | 'cache_first' | 'stale_while_revalidate';
export type CacheDuplicate = 'never' | 'always' | 'until_load' | 'auto';

Expand Down Expand Up @@ -79,6 +72,11 @@ export interface FetchOptions extends RequestInit {
queryParameters?: Record<string, string | number | boolean>;
}

let cacheStorage: Cache;
const cacheSupported = 'caches' in self;

const duplicateRequestStorage: Record<string, Promise<Response>> = {};

/**
* It fetches a JSON file from a URL, and returns the parsed data.
*
Expand All @@ -101,7 +99,7 @@ export async function getJson<ResponseType extends Record<string | number, unkno
const options = _processOptions(_options);
logger.logMethodArgs('getJson', {options});

const response = await _handleCacheStrategy(options);
const response = await _handleRemoveDuplicate(options);

let data: ResponseType;

Expand Down Expand Up @@ -148,7 +146,7 @@ export async function getJson<ResponseType extends Record<string | number, unkno
export function fetch(_options: Partial<FetchOptions> & {url: string}): Promise<Response> {
const options = _processOptions(_options);
logger.logMethodArgs('fetch', {options});
return _handleCacheStrategy(options);
return _handleRemoveDuplicate(options);
}

/**
Expand Down Expand Up @@ -198,21 +196,32 @@ function _processOptions(options: Partial<FetchOptions> & {url: string}): FetchO
return options as FetchOptions;
}

let cacheStorage: Cache;
const cacheSupported = 'caches' in self;
/**
* Handle Remove Duplicates over `_handleCacheStrategy`.
*/
function _handleRemoveDuplicate(options: FetchOptions): Promise<Response> {
if (options.removeDuplicate === 'never') return _handleCacheStrategy(options);

logger.logMethod('_handleRemoveDuplicate');

// const duplicateRequestStorage: Record<string, Promise<Response>> = {};
duplicateRequestStorage[options.url] ??= _handleCacheStrategy(options);

if (options.removeDuplicate === 'until_load') {
duplicateRequestStorage[options.url].then(() => delete duplicateRequestStorage[options.url]);
}

return duplicateRequestStorage[options.url];
}

/**
* Handle Cache Strategy over `_handleRetryPattern`.
*/
export async function _handleCacheStrategy(options: FetchOptions): Promise<Response> {
logger.logMethodArgs('_handleCacheStorage', {options});

async function _handleCacheStrategy(options: FetchOptions): Promise<Response> {
if (options.cacheStrategy === 'network_only') {
return _handleRetryPattern(options);
}
// else handle cache strategies!
logger.logMethod('_handleCacheStrategy');

if (cacheStorage == null) {
cacheStorage = await caches.open(options.cacheStorageName);
Expand Down Expand Up @@ -273,6 +282,8 @@ export async function _handleCacheStrategy(options: FetchOptions): Promise<Respo
* Handle retry pattern over `_handleTimeout`.
*/
async function _handleRetryPattern(options: FetchOptions): Promise<Response> {
if (!(options.retry >= 1)) return _handleTimeout(options);

logger.logMethod('_handleRetryPattern');

const externalAbortSignal = options.signal;
Expand Down Expand Up @@ -310,7 +321,7 @@ async function _handleRetryPattern(options: FetchOptions): Promise<Response> {
/**
* It's a wrapper around the browser's `fetch` with timeout.
*/
async function _handleTimeout(options: FetchOptions): Promise<Response> {
function _handleTimeout(options: FetchOptions): Promise<Response> {
logger.logMethod('_handleTimeout');
return new Promise((resolved, reject) => {
// @TODO: AbortController polyfill
Expand All @@ -337,7 +348,8 @@ async function _handleTimeout(options: FetchOptions): Promise<Response> {
});
});

window.fetch(options.url, options)
window
.fetch(options.url, options)
.then((response) => resolved(response))
.catch((reason) => reject(reason))
.finally(() => clearTimeout(timeoutId));
Expand Down