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