diff options
Diffstat (limited to 'src/stores/lib')
-rw-r--r-- | src/stores/lib/CachedRequest.ts (renamed from src/stores/lib/CachedRequest.js) | 49 | ||||
-rw-r--r-- | src/stores/lib/Request.ts (renamed from src/stores/lib/Request.js) | 106 |
2 files changed, 82 insertions, 73 deletions
diff --git a/src/stores/lib/CachedRequest.js b/src/stores/lib/CachedRequest.ts index a6dd47f7d..25cc365e2 100644 --- a/src/stores/lib/CachedRequest.js +++ b/src/stores/lib/CachedRequest.ts | |||
@@ -3,29 +3,33 @@ import { isEqual, remove } from 'lodash'; | |||
3 | import Request from './Request'; | 3 | import Request from './Request'; |
4 | 4 | ||
5 | export default class CachedRequest extends Request { | 5 | export default class CachedRequest extends Request { |
6 | _apiCalls = []; | 6 | _apiCalls: any[] = []; |
7 | 7 | ||
8 | _isInvalidated = true; | 8 | _isInvalidated = true; |
9 | 9 | ||
10 | execute(...callArgs) { | 10 | execute(...callArgs): this { |
11 | // Do not continue if this request is already loading | 11 | // Do not continue if this request is already loading |
12 | if (this._isWaitingForResponse) return this; | 12 | if (this.isWaitingForResponse) { |
13 | return this; | ||
14 | } | ||
13 | 15 | ||
14 | // Very simple caching strategy -> only continue if the call / args changed | 16 | // Very simple caching strategy -> only continue if the call / args changed |
15 | // or the request was invalidated manually from outside | 17 | // or the request was invalidated manually from outside |
16 | const existingApiCall = this._findApiCall(callArgs); | 18 | const existingApiCall = this._findApiCall(callArgs); |
17 | 19 | ||
18 | // Invalidate if new or different api call will be done | 20 | // Invalidate if new or different api call will be done |
19 | if (existingApiCall && existingApiCall !== this._currentApiCall) { | 21 | if (existingApiCall && existingApiCall !== this.currentApiCall) { |
20 | this._isInvalidated = true; | 22 | this._isInvalidated = true; |
21 | this._currentApiCall = existingApiCall; | 23 | this.currentApiCall = existingApiCall; |
22 | } else if (!existingApiCall) { | 24 | } else if (!existingApiCall) { |
23 | this._isInvalidated = true; | 25 | this._isInvalidated = true; |
24 | this._currentApiCall = this._addApiCall(callArgs); | 26 | this.currentApiCall = this._addApiCall(callArgs); |
25 | } | 27 | } |
26 | 28 | ||
27 | // Do not continue if this request is not invalidated (see above) | 29 | // Do not continue if this request is not invalidated (see above) |
28 | if (!this._isInvalidated) return this; | 30 | if (!this._isInvalidated) { |
31 | return this; | ||
32 | } | ||
29 | 33 | ||
30 | // This timeout is necessary to avoid warnings from mobx | 34 | // This timeout is necessary to avoid warnings from mobx |
31 | // regarding triggering actions as side-effect of getters | 35 | // regarding triggering actions as side-effect of getters |
@@ -41,18 +45,18 @@ export default class CachedRequest extends Request { | |||
41 | ); | 45 | ); |
42 | 46 | ||
43 | // Issue api call & save it as promise that is handled to update the results of the operation | 47 | // Issue api call & save it as promise that is handled to update the results of the operation |
44 | this._promise = new Promise(resolve => { | 48 | this.promise = new Promise(resolve => { |
45 | this._api[this._method](...callArgs) | 49 | this.api[this.method](...callArgs) |
46 | .then(result => { | 50 | .then(result => { |
47 | setTimeout( | 51 | setTimeout( |
48 | action(() => { | 52 | action(() => { |
49 | this.result = result; | 53 | this.result = result; |
50 | if (this._currentApiCall) this._currentApiCall.result = result; | 54 | if (this.currentApiCall) this.currentApiCall.result = result; |
51 | this.isExecuting = false; | 55 | this.isExecuting = false; |
52 | this.isError = false; | 56 | this.isError = false; |
53 | this.wasExecuted = true; | 57 | this.wasExecuted = true; |
54 | this._isInvalidated = false; | 58 | this._isInvalidated = false; |
55 | this._isWaitingForResponse = false; | 59 | this.isWaitingForResponse = false; |
56 | this._triggerHooks(); | 60 | this._triggerHooks(); |
57 | resolve(result); | 61 | resolve(result); |
58 | }), | 62 | }), |
@@ -68,7 +72,7 @@ export default class CachedRequest extends Request { | |||
68 | this.isExecuting = false; | 72 | this.isExecuting = false; |
69 | this.isError = true; | 73 | this.isError = true; |
70 | this.wasExecuted = true; | 74 | this.wasExecuted = true; |
71 | this._isWaitingForResponse = false; | 75 | this.isWaitingForResponse = false; |
72 | this._triggerHooks(); | 76 | this._triggerHooks(); |
73 | // reject(error); | 77 | // reject(error); |
74 | }), | 78 | }), |
@@ -78,26 +82,27 @@ export default class CachedRequest extends Request { | |||
78 | ); | 82 | ); |
79 | }); | 83 | }); |
80 | 84 | ||
81 | this._isWaitingForResponse = true; | 85 | this.isWaitingForResponse = true; |
82 | return this; | 86 | return this; |
83 | } | 87 | } |
84 | 88 | ||
85 | // eslint-disable-next-line unicorn/no-object-as-default-parameter | 89 | static defaultOptions = { immediately: false }; |
86 | invalidate(options = { immediately: false }) { | 90 | |
91 | invalidate(options = CachedRequest.defaultOptions): this { | ||
87 | this._isInvalidated = true; | 92 | this._isInvalidated = true; |
88 | if (options.immediately && this._currentApiCall) { | 93 | if (options.immediately && this.currentApiCall) { |
89 | return this.execute(...this._currentApiCall.args); | 94 | return this.execute(...this.currentApiCall.args); |
90 | } | 95 | } |
91 | return this; | 96 | return this; |
92 | } | 97 | } |
93 | 98 | ||
94 | patch(modify) { | 99 | patch(modify): Promise<this> { |
95 | return new Promise(resolve => { | 100 | return new Promise(resolve => { |
96 | setTimeout( | 101 | setTimeout( |
97 | action(() => { | 102 | action(() => { |
98 | const override = modify(this.result); | 103 | const override = modify(this.result); |
99 | if (override !== undefined) this.result = override; | 104 | if (override !== undefined) this.result = override; |
100 | if (this._currentApiCall) this._currentApiCall.result = this.result; | 105 | if (this.currentApiCall) this.currentApiCall.result = this.result; |
101 | resolve(this); | 106 | resolve(this); |
102 | }), | 107 | }), |
103 | 0, | 108 | 0, |
@@ -105,17 +110,17 @@ export default class CachedRequest extends Request { | |||
105 | }); | 110 | }); |
106 | } | 111 | } |
107 | 112 | ||
108 | removeCacheForCallWith(...args) { | 113 | removeCacheForCallWith(...args: any): void { |
109 | remove(this._apiCalls, c => isEqual(c.args, args)); | 114 | remove(this._apiCalls, c => isEqual(c.args, args)); |
110 | } | 115 | } |
111 | 116 | ||
112 | _addApiCall(args) { | 117 | _addApiCall(args: any) { |
113 | const newCall = { args, result: null }; | 118 | const newCall = { args, result: null }; |
114 | this._apiCalls.push(newCall); | 119 | this._apiCalls.push(newCall); |
115 | return newCall; | 120 | return newCall; |
116 | } | 121 | } |
117 | 122 | ||
118 | _findApiCall(args) { | 123 | _findApiCall(args: any) { |
119 | return this._apiCalls.find(c => isEqual(c.args, args)); | 124 | return this._apiCalls.find(c => isEqual(c.args, args)); |
120 | } | 125 | } |
121 | } | 126 | } |
diff --git a/src/stores/lib/Request.js b/src/stores/lib/Request.ts index 60c943a42..f9424ac99 100644 --- a/src/stores/lib/Request.js +++ b/src/stores/lib/Request.ts | |||
@@ -1,16 +1,18 @@ | |||
1 | import { observable, action, computed, makeObservable } from 'mobx'; | 1 | import { observable, action, computed, makeObservable } from 'mobx'; |
2 | import { isEqual } from 'lodash/fp'; | 2 | import { isEqual } from 'lodash/fp'; |
3 | 3 | ||
4 | type Hook = (request: Request) => void; | ||
5 | |||
4 | export default class Request { | 6 | export default class Request { |
5 | static _hooks = []; | 7 | static _hooks: Hook[] = []; |
6 | 8 | ||
7 | static registerHook(hook) { | 9 | static registerHook(hook: Hook) { |
8 | Request._hooks.push(hook); | 10 | Request._hooks.push(hook); |
9 | } | 11 | } |
10 | 12 | ||
11 | @observable result = null; | 13 | @observable result: any = null; |
12 | 14 | ||
13 | @observable error = null; | 15 | @observable error: any = null; |
14 | 16 | ||
15 | @observable isExecuting = false; | 17 | @observable isExecuting = false; |
16 | 18 | ||
@@ -18,43 +20,47 @@ export default class Request { | |||
18 | 20 | ||
19 | @observable wasExecuted = false; | 21 | @observable wasExecuted = false; |
20 | 22 | ||
21 | @action _reset() { | 23 | promise: any = Promise; |
22 | this.error = null; | ||
23 | this.result = null; | ||
24 | this.isExecuting = false; | ||
25 | this.isError = false; | ||
26 | this.wasExecuted = false; | ||
27 | this._isWaitingForResponse = false; | ||
28 | this._promise = Promise; | ||
29 | 24 | ||
30 | return this; | 25 | protected api: any = {}; |
31 | } | ||
32 | 26 | ||
33 | _promise = Promise; | 27 | method = ''; |
34 | 28 | ||
35 | _api = {}; | 29 | protected isWaitingForResponse = false; |
36 | 30 | ||
37 | _method = ''; | 31 | protected currentApiCall: any = null; |
38 | 32 | ||
39 | _isWaitingForResponse = false; | 33 | retry = () => this.reload(); |
40 | 34 | ||
41 | _currentApiCall = null; | 35 | reset = () => this._reset(); |
42 | 36 | ||
43 | constructor(api, method) { | 37 | constructor(api, method) { |
44 | makeObservable(this); | 38 | makeObservable(this); |
45 | 39 | ||
46 | this._api = api; | 40 | this.api = api; |
47 | this._method = method; | 41 | this.method = method; |
42 | } | ||
43 | |||
44 | @action _reset(): this { | ||
45 | this.error = null; | ||
46 | this.result = null; | ||
47 | this.isExecuting = false; | ||
48 | this.isError = false; | ||
49 | this.wasExecuted = false; | ||
50 | this.isWaitingForResponse = false; | ||
51 | this.promise = Promise; | ||
52 | |||
53 | return this; | ||
48 | } | 54 | } |
49 | 55 | ||
50 | execute(...callArgs) { | 56 | execute(...callArgs: any[]): this { |
51 | // Do not continue if this request is already loading | 57 | // Do not continue if this request is already loading |
52 | if (this._isWaitingForResponse) return this; | 58 | if (this.isWaitingForResponse) return this; |
53 | 59 | ||
54 | if (!this._api[this._method]) { | 60 | if (!this.api[this.method]) { |
55 | throw new Error( | 61 | throw new Error( |
56 | `Missing method <${this._method}> on api object:`, | 62 | `Missing method <${this.method}> on api object:`, |
57 | this._api, | 63 | this.api, |
58 | ); | 64 | ); |
59 | } | 65 | } |
60 | 66 | ||
@@ -68,18 +74,18 @@ export default class Request { | |||
68 | ); | 74 | ); |
69 | 75 | ||
70 | // Issue api call & save it as promise that is handled to update the results of the operation | 76 | // Issue api call & save it as promise that is handled to update the results of the operation |
71 | this._promise = new Promise((resolve, reject) => { | 77 | this.promise = new Promise((resolve, reject) => { |
72 | this._api[this._method](...callArgs) | 78 | this.api[this.method](...callArgs) |
73 | .then(result => { | 79 | .then(result => { |
74 | setTimeout( | 80 | setTimeout( |
75 | action(() => { | 81 | action(() => { |
76 | this.error = null; | 82 | this.error = null; |
77 | this.result = result; | 83 | this.result = result; |
78 | if (this._currentApiCall) this._currentApiCall.result = result; | 84 | if (this.currentApiCall) this.currentApiCall.result = result; |
79 | this.isExecuting = false; | 85 | this.isExecuting = false; |
80 | this.isError = false; | 86 | this.isError = false; |
81 | this.wasExecuted = true; | 87 | this.wasExecuted = true; |
82 | this._isWaitingForResponse = false; | 88 | this.isWaitingForResponse = false; |
83 | this._triggerHooks(); | 89 | this._triggerHooks(); |
84 | resolve(result); | 90 | resolve(result); |
85 | }), | 91 | }), |
@@ -95,7 +101,7 @@ export default class Request { | |||
95 | this.isExecuting = false; | 101 | this.isExecuting = false; |
96 | this.isError = true; | 102 | this.isError = true; |
97 | this.wasExecuted = true; | 103 | this.wasExecuted = true; |
98 | this._isWaitingForResponse = false; | 104 | this.isWaitingForResponse = false; |
99 | this._triggerHooks(); | 105 | this._triggerHooks(); |
100 | reject(error); | 106 | reject(error); |
101 | }), | 107 | }), |
@@ -105,51 +111,49 @@ export default class Request { | |||
105 | ); | 111 | ); |
106 | }); | 112 | }); |
107 | 113 | ||
108 | this._isWaitingForResponse = true; | 114 | this.isWaitingForResponse = true; |
109 | this._currentApiCall = { args: callArgs, result: null }; | 115 | this.currentApiCall = { args: callArgs, result: null }; |
110 | return this; | 116 | return this; |
111 | } | 117 | } |
112 | 118 | ||
113 | reload() { | 119 | reload(): this { |
114 | const args = this._currentApiCall ? this._currentApiCall.args : []; | 120 | const args = this.currentApiCall ? this.currentApiCall.args : []; |
115 | this.error = null; | 121 | this.error = null; |
116 | return this.execute(...args); | 122 | return this.execute(...args); |
117 | } | 123 | } |
118 | 124 | ||
119 | retry = () => this.reload(); | 125 | isExecutingWithArgs(...args: any[]): boolean { |
120 | |||
121 | isExecutingWithArgs(...args) { | ||
122 | return ( | 126 | return ( |
123 | this.isExecuting && | 127 | this.isExecuting && |
124 | this._currentApiCall && | 128 | this.currentApiCall && |
125 | isEqual(this._currentApiCall.args, args) | 129 | isEqual(this.currentApiCall.args, args) |
126 | ); | 130 | ); |
127 | } | 131 | } |
128 | 132 | ||
129 | @computed get isExecutingFirstTime() { | 133 | @computed get isExecutingFirstTime(): boolean { |
130 | return !this.wasExecuted && this.isExecuting; | 134 | return !this.wasExecuted && this.isExecuting; |
131 | } | 135 | } |
132 | 136 | ||
133 | /* eslint-disable unicorn/no-thenable */ | 137 | /* eslint-disable unicorn/no-thenable */ |
134 | then(...args) { | 138 | then(...args: any[]) { |
135 | if (!this._promise) | 139 | if (!this.promise) |
136 | throw new Error( | 140 | throw new Error( |
137 | 'You have to call Request::execute before you can access it as promise', | 141 | 'You have to call Request::execute before you can access it as promise', |
138 | ); | 142 | ); |
139 | return this._promise.then(...args); | 143 | return this.promise.then(...args); |
140 | } | 144 | } |
141 | 145 | ||
142 | catch(...args) { | 146 | catch(...args: any[]) { |
143 | if (!this._promise) | 147 | if (!this.promise) |
144 | throw new Error( | 148 | throw new Error( |
145 | 'You have to call Request::execute before you can access it as promise', | 149 | 'You have to call Request::execute before you can access it as promise', |
146 | ); | 150 | ); |
147 | return this._promise.catch(...args); | 151 | return this.promise.catch(...args); |
148 | } | 152 | } |
149 | 153 | ||
150 | _triggerHooks() { | 154 | _triggerHooks(): void { |
151 | for (const hook of Request._hooks) hook(this); | 155 | for (const hook of Request._hooks) { |
156 | hook(this); | ||
157 | } | ||
152 | } | 158 | } |
153 | |||
154 | reset = () => this._reset(); | ||
155 | } | 159 | } |