aboutsummaryrefslogtreecommitdiffstats
path: root/src/stores/lib/Request.js
blob: 486de8a496579fdd973d13fc87bc2ebb3c51416a (plain) (blame)
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
116
117
118
119
120
121
122
import { observable, action, computed } from 'mobx';
import { isEqual } from 'lodash/fp';

export default class Request {
  static _hooks = [];

  static registerHook(hook) {
    Request._hooks.push(hook);
  }

  @observable result = null;

  @observable error = null;

  @observable isExecuting = false;

  @observable isError = false;

  @observable wasExecuted = false;

  _promise = Promise;

  _api = {};

  _method = '';

  _isWaitingForResponse = false;

  _currentApiCall = null;

  constructor(api, method) {
    this._api = api;
    this._method = method;
  }

  execute(...callArgs) {
    // Do not continue if this request is already loading
    if (this._isWaitingForResponse) return this;

    if (!this._api[this._method]) {
      throw new Error(`Missing method <${this._method}> on api object:`, this._api);
    }

    // This timeout is necessary to avoid warnings from mobx
    // regarding triggering actions as side-effect of getters
    setTimeout(action(() => {
      this.isExecuting = true;
    }), 0);

    // Issue api call & save it as promise that is handled to update the results of the operation
    this._promise = new Promise((resolve, reject) => {
      this._api[this._method](...callArgs)
        .then((result) => {
          setTimeout(action(() => {
            this.result = result;
            if (this._currentApiCall) this._currentApiCall.result = result;
            this.isExecuting = false;
            this.isError = false;
            this.wasExecuted = true;
            this._isWaitingForResponse = false;
            this._triggerHooks();
            resolve(result);
          }), 1);
          return result;
        })
        .catch(action((error) => {
          setTimeout(action(() => {
            this.error = error;
            this.isExecuting = false;
            this.isError = true;
            this.wasExecuted = true;
            this._isWaitingForResponse = false;
            this._triggerHooks();
            reject(error);
          }), 1);
        }));
    });

    this._isWaitingForResponse = true;
    this._currentApiCall = { args: callArgs, result: null };
    return this;
  }

  reload() {
    return this.execute(...this._currentApiCall.args);
  }

  retry = () => this.reload();

  isExecutingWithArgs(...args) {
    return this.isExecuting && this._currentApiCall && isEqual(this._currentApiCall.args, args);
  }

  @computed get isExecutingFirstTime() {
    return !this.wasExecuted && this.isExecuting;
  }

  then(...args) {
    if (!this._promise) throw new Error('You have to call Request::execute before you can access it as promise');
    return this._promise.then(...args);
  }

  catch(...args) {
    if (!this._promise) throw new Error('You have to call Request::execute before you can access it as promise');
    return this._promise.catch(...args);
  }

  _triggerHooks() {
    Request._hooks.forEach(hook => hook(this));
  }

  reset = () => {
    this.result = null;
    this.isExecuting = false;
    this.isError = false;
    this.wasExecuted = false;
    this._isWaitingForResponse = false;
    this._promise = Promise;

    return this;
  };
}