forked from arthurfiorette/axios-cache-interceptor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
request.ts
115 lines (97 loc) Β· 3.17 KB
/
request.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import { deferred } from 'typed-core/dist/promises/deferred';
import type {
AxiosCacheInstance,
CacheAxiosResponse,
CacheRequestConfig
} from '../cache/axios';
import type {
CachedResponse,
CachedStorageValue,
LoadingStorageValue
} from '../storage/types';
import type { AxiosInterceptor } from './types';
export class CacheRequestInterceptor<D>
implements AxiosInterceptor<CacheRequestConfig<D>>
{
constructor(readonly axios: AxiosCacheInstance) {}
use = (): void => {
this.axios.interceptors.request.use(this.onFulfilled);
};
onFulfilled = async (config: CacheRequestConfig<D>): Promise<CacheRequestConfig<D>> => {
// Skip cache
if (config.cache === false) {
return config;
}
// Only cache specified methods
const allowedMethods = config.cache?.methods || this.axios.defaults.cache.methods;
if (
!allowedMethods.some((method) => (config.method || 'get').toLowerCase() == method)
) {
return config;
}
const key = this.axios.generateKey(config);
// Assumes that the storage handled staled responses
let cache = await this.axios.storage.get(key);
// Not cached, continue the request, and mark it as fetching
emptyState: if (cache.state == 'empty') {
/**
* 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 (this.axios.waiting[key]) {
cache = (await this.axios.storage.get(key)) as
| CachedStorageValue
| LoadingStorageValue;
break emptyState;
}
// Create a deferred to resolve other requests for the same key when it's completed
this.axios.waiting[key] = deferred();
/**
* Add a default reject handler to catch when the request is
* aborted without others waiting for it.
*/
this.axios.waiting[key]?.catch(() => undefined);
await this.axios.storage.set(key, {
state: 'loading',
ttl: config.cache?.ttl
});
return config;
}
let cachedResponse: CachedResponse;
if (cache.state === 'loading') {
const deferred = this.axios.waiting[key];
/**
* If the deferred is undefined, means that the outside has
* removed that key from the waiting list
*/
if (!deferred) {
await this.axios.storage.remove(key);
return config;
}
try {
cachedResponse = await deferred;
} catch (e) {
// 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<any, D>>({
config: config,
data: cachedResponse.data,
headers: cachedResponse.headers,
status: cachedResponse.status,
statusText: cachedResponse.statusText,
cached: true,
id: key
});
return config;
};
}