From 58cda9cc7fb79ca9df6746de7f9662bc08dc156a Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Fri, 13 Oct 2017 12:29:40 +0200 Subject: initial commit --- src/stores/lib/CachedRequest.js | 106 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/stores/lib/CachedRequest.js (limited to 'src/stores/lib/CachedRequest.js') 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 @@ +// @flow +import { action } from 'mobx'; +import { isEqual, remove } from 'lodash'; +import Request from './Request'; + +export default class CachedRequest extends Request { + _apiCalls = []; + _isInvalidated = true; + + execute(...callArgs) { + // Do not continue if this request is already loading + if (this._isWaitingForResponse) return this; + + // Very simple caching strategy -> only continue if the call / args changed + // or the request was invalidated manually from outside + const existingApiCall = this._findApiCall(callArgs); + + // Invalidate if new or different api call will be done + if (existingApiCall && existingApiCall !== this._currentApiCall) { + this._isInvalidated = true; + this._currentApiCall = existingApiCall; + } else if (!existingApiCall) { + this._isInvalidated = true; + this._currentApiCall = this._addApiCall(callArgs); + } + + // Do not continue if this request is not invalidated (see above) + if (!this._isInvalidated) return this; + + // This timeout is necessary to avoid warnings from mobx + // regarding triggering actions as side-effect of getters + setTimeout(action(() => { + this.isExecuting = true; + // Apply the previous result from this call immediately (cached) + if (existingApiCall) { + this.result = existingApiCall.result; + } + }), 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._isInvalidated = false; + 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; + return this; + } + + invalidate(options = { immediately: false }) { + this._isInvalidated = true; + if (options.immediately && this._currentApiCall) { + return this.execute(...this._currentApiCall.args); + } + return this; + } + + patch(modify) { + return new Promise((resolve) => { + setTimeout(action(() => { + const override = modify(this.result); + if (override !== undefined) this.result = override; + if (this._currentApiCall) this._currentApiCall.result = this.result; + resolve(this); + }), 0); + }); + } + + removeCacheForCallWith(...args) { + remove(this._apiCalls, c => isEqual(c.args, args)); + } + + _addApiCall(args) { + const newCall = { args, result: null }; + this._apiCalls.push(newCall); + return newCall; + } + + _findApiCall(args) { + return this._apiCalls.find(c => isEqual(c.args, args)); + } +} -- cgit v1.2.3-70-g09d2