aboutsummaryrefslogtreecommitdiffstats
path: root/src/stores/lib/CachedRequest.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/stores/lib/CachedRequest.js')
-rw-r--r--src/stores/lib/CachedRequest.js106
1 files changed, 106 insertions, 0 deletions
diff --git a/src/stores/lib/CachedRequest.js b/src/stores/lib/CachedRequest.js
new file mode 100644
index 000000000..c0c3d40a1
--- /dev/null
+++ b/src/stores/lib/CachedRequest.js
@@ -0,0 +1,106 @@
1// @flow
2import { action } from 'mobx';
3import { isEqual, remove } from 'lodash';
4import Request from './Request';
5
6export default class CachedRequest extends Request {
7 _apiCalls = [];
8 _isInvalidated = true;
9
10 execute(...callArgs) {
11 // Do not continue if this request is already loading
12 if (this._isWaitingForResponse) return this;
13
14 // Very simple caching strategy -> only continue if the call / args changed
15 // or the request was invalidated manually from outside
16 const existingApiCall = this._findApiCall(callArgs);
17
18 // Invalidate if new or different api call will be done
19 if (existingApiCall && existingApiCall !== this._currentApiCall) {
20 this._isInvalidated = true;
21 this._currentApiCall = existingApiCall;
22 } else if (!existingApiCall) {
23 this._isInvalidated = true;
24 this._currentApiCall = this._addApiCall(callArgs);
25 }
26
27 // Do not continue if this request is not invalidated (see above)
28 if (!this._isInvalidated) return this;
29
30 // This timeout is necessary to avoid warnings from mobx
31 // regarding triggering actions as side-effect of getters
32 setTimeout(action(() => {
33 this.isExecuting = true;
34 // Apply the previous result from this call immediately (cached)
35 if (existingApiCall) {
36 this.result = existingApiCall.result;
37 }
38 }), 0);
39
40 // Issue api call & save it as promise that is handled to update the results of the operation
41 this._promise = new Promise((resolve, reject) => {
42 this._api[this._method](...callArgs)
43 .then((result) => {
44 setTimeout(action(() => {
45 this.result = result;
46 if (this._currentApiCall) this._currentApiCall.result = result;
47 this.isExecuting = false;
48 this.isError = false;
49 this.wasExecuted = true;
50 this._isInvalidated = false;
51 this._isWaitingForResponse = false;
52 this._triggerHooks();
53 resolve(result);
54 }), 1);
55 return result;
56 })
57 .catch(action((error) => {
58 setTimeout(action(() => {
59 this.error = error;
60 this.isExecuting = false;
61 this.isError = true;
62 this.wasExecuted = true;
63 this._isWaitingForResponse = false;
64 this._triggerHooks();
65 reject(error);
66 }), 1);
67 }));
68 });
69
70 this._isWaitingForResponse = true;
71 return this;
72 }
73
74 invalidate(options = { immediately: false }) {
75 this._isInvalidated = true;
76 if (options.immediately && this._currentApiCall) {
77 return this.execute(...this._currentApiCall.args);
78 }
79 return this;
80 }
81
82 patch(modify) {
83 return new Promise((resolve) => {
84 setTimeout(action(() => {
85 const override = modify(this.result);
86 if (override !== undefined) this.result = override;
87 if (this._currentApiCall) this._currentApiCall.result = this.result;
88 resolve(this);
89 }), 0);
90 });
91 }
92
93 removeCacheForCallWith(...args) {
94 remove(this._apiCalls, c => isEqual(c.args, args));
95 }
96
97 _addApiCall(args) {
98 const newCall = { args, result: null };
99 this._apiCalls.push(newCall);
100 return newCall;
101 }
102
103 _findApiCall(args) {
104 return this._apiCalls.find(c => isEqual(c.args, args));
105 }
106}