Created
March 20, 2020 19:38
-
-
Save adsurov/327ffcc926e72301921dfbc4804626b9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class Observable { | |
| constructor(value, cloneValue) { | |
| this._cloneValue = cloneValue || defaultCloneValue; | |
| this._curValue = this._cloneValue(value); | |
| this._listeners = []; // I think it could be better to use object instead | |
| this._obsoleteListeners = []; | |
| function defaultCloneValue(v) { | |
| try { | |
| return JSON.parse(JSON.stringify(v)) // better use special deep cloningfunction to avoid circular links | |
| } catch (err) { | |
| throw new Error('error with cloning object', err) | |
| }; | |
| } | |
| } | |
| /** | |
| * Adds a listener that is notified of value changes (when `fireChanged()` is called). | |
| * A listener is a function that accepts the old value and the new value. | |
| * If the listener needs to unsubscribe, it can return `false`, all other values | |
| * will be ignored. | |
| * Parameters: listener function. | |
| */ | |
| addListener(listenerFun) { | |
| this._listeners.push({f: listenerFun, delay: null}); | |
| } | |
| /** | |
| * Adds an asynchronous listener that is notified of value changes (when `fireChanged()` is called) | |
| * not more frequently than once in `delay` ms. | |
| * A listener is a function that accepts the old value and the new value. | |
| * If the listener needs to unsubscribe, it can return `false`, all other values | |
| * will be ignored. | |
| * Parameters: listener function, delay in ms. | |
| */ | |
| addAsyncListener(listenerFun, delay) { | |
| this._listeners.push({f: listenerFun, delay: delay}); | |
| } | |
| removeListener(listenerFun) { | |
| this._listeners = this._listeners.filter(listener => listener.f === listenerFun); | |
| } | |
| // move to separate method for better testing and readability | |
| callListener(oldValue, newValue, listener) { | |
| const shouldUnsubscribe = !listener.f(oldValue, newValue) // call data and unsubscribe if linstener wants it. | |
| if (shouldUnsubscribe) { | |
| this._obsoleteListeners.push(listener); // unsubscribe current value | |
| } | |
| } | |
| fireChanged(newValue) { | |
| // we should check if there old listeners from previous changes (includes async) | |
| if (this._obsoleteListeners.length > 0) { | |
| this._obsoleteListeners.forEach(listener => this.removeListener(listener.f)) | |
| this._obsoleteListeners = [] | |
| } | |
| const curValue = this._curValue; // we never reassign this value | |
| const listeners = this._listeners; // we mutate this._listeners object - it is no good | |
| for (let i = 0; i < listeners.length; i++) { // to start from beginning i++ and 'let' for variable declaration | |
| const listener = listeners[i]; | |
| const { delay } = listener; | |
| if (!delay && delay !== 0) { | |
| this.callListener(curValue, newValue, listener); | |
| continue; | |
| } | |
| let job = listener.job; | |
| if (job) { | |
| clearTimeout(job); | |
| } | |
| listener.job = setTimeout( | |
| function callListenerAsync() { | |
| listener.job = null; | |
| this.callListener(curValue, newValue, listener); | |
| }.bind(this), // to avoid loose the context | |
| delay | |
| ); | |
| } | |
| this._curValue = this._cloneValue(newValue); | |
| } | |
| } | |
| export default Observable; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment