Skip to content

Commit

Permalink
refactor: removed "abstract" buildInterceptor function
Browse files Browse the repository at this point in the history
  • Loading branch information
arthurfiorette committed Jan 8, 2022
1 parent d30b862 commit f30e262
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 221 deletions.
29 changes: 2 additions & 27 deletions src/interceptors/build.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,10 @@
import type {
AxiosCacheInstance,
CacheAxiosResponse,
CacheRequestConfig
} from '../cache/axios';
import type { CacheAxiosResponse, CacheRequestConfig } from '../cache/axios';

export interface AxiosInterceptor<T> {
onFulfilled?(value: T): T | Promise<T>;
onRejected?(error: any): any;
apply: (axios: AxiosCacheInstance) => void;
apply: () => void;
}

export type RequestInterceptor = AxiosInterceptor<CacheRequestConfig<unknown, unknown>>;
export type ResponseInterceptor = AxiosInterceptor<CacheAxiosResponse<unknown, unknown>>;

export function buildInterceptor(
type: 'request',
interceptor: Omit<RequestInterceptor, 'apply'>
): RequestInterceptor;

export function buildInterceptor(
type: 'response',
interceptor: Omit<ResponseInterceptor, 'apply'>
): ResponseInterceptor;

export function buildInterceptor(
type: 'request' | 'response',
{ onFulfilled, onRejected }: Omit<AxiosInterceptor<unknown>, 'apply'>
): AxiosInterceptor<unknown> {
return {
onFulfilled,
onRejected,
apply: (axios) => axios.interceptors[type].use(onFulfilled, onRejected)
};
}
173 changes: 88 additions & 85 deletions src/interceptors/request.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { deferred } from 'fast-defer';
import { buildInterceptor } from '..';
import type { AxiosCacheInstance, CacheAxiosResponse } from '../cache/axios';
import type {
CachedResponse,
CachedStorageValue,
LoadingStorageValue
} from '../storage/types';
import type { RequestInterceptor } from './build';
import {
ConfigWithCache,
createValidateStatus,
Expand All @@ -14,99 +14,102 @@ import {
} from './util';

export function defaultRequestInterceptor(axios: AxiosCacheInstance) {
return buildInterceptor('request', {
onFulfilled: async (config) => {
if (config.cache === false) {
return config;
}
const onFulfilled: RequestInterceptor['onFulfilled'] = async (config) => {
if (config.cache === false) {
return config;
}

// merge defaults with per request configuration
config.cache = { ...axios.defaults.cache, ...config.cache };
// merge defaults with per request configuration
config.cache = { ...axios.defaults.cache, ...config.cache };

if (!isMethodIn(config.method, config.cache.methods)) {
return config;
if (!isMethodIn(config.method, config.cache.methods)) {
return config;
}

const key = axios.generateKey(config);

// Assumes that the storage handled staled responses
let cache = await axios.storage.get(key);

// Not cached, continue the request, and mark it as fetching
emptyOrStale: if (cache.state == 'empty' || cache.state === 'stale') {
/**
* This checks for simultaneous access to a new key. The js event loop jumps on the
* first await statement, so the second (asynchronous call) request may have already
* started executing.
*/
if (axios.waiting[key]) {
cache = (await axios.storage.get(key)) as
| CachedStorageValue
| LoadingStorageValue;
break emptyOrStale;
}

const key = axios.generateKey(config);

// Assumes that the storage handled staled responses
let cache = await axios.storage.get(key);

// Not cached, continue the request, and mark it as fetching
emptyOrStale: if (cache.state == 'empty' || cache.state === 'stale') {
/**
* This checks for simultaneous access to a new key. The js event loop jumps on
* the first await statement, so the second (asynchronous call) request may have
* already started executing.
*/
if (axios.waiting[key]) {
cache = (await axios.storage.get(key)) as
| CachedStorageValue
| LoadingStorageValue;
break emptyOrStale;
}

// Create a deferred to resolve other requests for the same key when it's completed
axios.waiting[key] = deferred();

/**
* Add a default reject handler to catch when the request is aborted without
* others waiting for it.
*/
axios.waiting[key]?.catch(() => undefined);

await axios.storage.set(key, {
state: 'loading',
data: cache.data
});

if (cache.state === 'stale') {
setRevalidationHeaders(cache, config as ConfigWithCache<unknown>);
}

config.validateStatus = createValidateStatus(config.validateStatus);
// Create a deferred to resolve other requests for the same key when it's completed
axios.waiting[key] = deferred();

return config;
}
/**
* Add a default reject handler to catch when the request is aborted without others
* waiting for it.
*/
axios.waiting[key]?.catch(() => undefined);

await axios.storage.set(key, {
state: 'loading',
data: cache.data
});

let cachedResponse: CachedResponse;

if (cache.state === 'loading') {
const deferred = axios.waiting[key];

// Just in case, the deferred doesn't exists.
/* istanbul ignore if 'really hard to test' */
if (!deferred) {
await axios.storage.remove(key);
return config;
}

try {
cachedResponse = await deferred;
} catch {
// The deferred is rejected when the request that we are waiting rejected cache.
return config;
}
} else {
cachedResponse = cache.data;
if (cache.state === 'stale') {
setRevalidationHeaders(cache, config as ConfigWithCache<unknown>);
}

config.adapter = () =>
/**
* Even though the response interceptor receives this one from here, it has been
* configured to ignore cached responses: true
*/
Promise.resolve<CacheAxiosResponse<unknown, unknown>>({
config,
data: cachedResponse.data,
headers: cachedResponse.headers,
status: cachedResponse.status,
statusText: cachedResponse.statusText,
cached: true,
id: key
});
config.validateStatus = createValidateStatus(config.validateStatus);

return config;
}
});

let cachedResponse: CachedResponse;

if (cache.state === 'loading') {
const deferred = axios.waiting[key];

// Just in case, the deferred doesn't exists.
/* istanbul ignore if 'really hard to test' */
if (!deferred) {
await axios.storage.remove(key);
return config;
}

try {
cachedResponse = await deferred;
} catch {
// The deferred is rejected when the request that we are waiting rejected cache.
return config;
}
} else {
cachedResponse = cache.data;
}

config.adapter = () =>
/**
* Even though the response interceptor receives this one from here, it has been
* configured to ignore cached responses: true
*/
Promise.resolve<CacheAxiosResponse<unknown, unknown>>({
config,
data: cachedResponse.data,
headers: cachedResponse.headers,
status: cachedResponse.status,
statusText: cachedResponse.statusText,
cached: true,
id: key
});

return config;
};

return {
onFulfilled,
apply: () => axios.interceptors.request.use(onFulfilled)
};
}
Loading

0 comments on commit f30e262

Please sign in to comment.