/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** * The PaymentsStore class provides lightweight storage with an async publish/subscribe mechanism. * Synchronous state changes are batched to improve application performance and to reduce partial * state propagation. */ export default class PaymentsStore { /** * @param {object} [defaultState = {}] The initial state of the store. */ constructor(defaultState = {}) { this._defaultState = Object.assign({}, defaultState); this._state = defaultState; this._nextNotifification = 0; this._subscribers = new Set(); } /** * Get the current state as a shallow clone with a shallow freeze. * You shouldn't modify any part of the returned state object as that would bypass notifying * subscribers and could lead to subscribers assuming old state. * * @returns {Object} containing the current state */ getState() { return Object.freeze(Object.assign({}, this._state)); } /** * Used for testing to reset to the default state from the constructor. * @returns {Promise} returned by setState. */ async reset() { return this.setState(this._defaultState); } /** * Augment the current state with the keys of `obj` and asynchronously notify * state subscribers. As a result, multiple synchronous state changes will lead * to a single subscriber notification which leads to better performance and * reduces partial state changes. * * @param {Object} obj The object to augment the state with. Keys in the object * will be shallow copied with Object.assign. * * @example If the state is currently {a:3} then setState({b:"abc"}) will result in a state of * {a:3, b:"abc"}. */ async setState(obj) { Object.assign(this._state, obj); let thisChangeNum = ++this._nextNotifification; // Let any synchronous setState calls that happen after the current setState call // complete first. // Their effects on the state will be batched up before the callback is actually called below. await Promise.resolve(); // Don't notify for state changes that are no longer the most recent. We only want to call the // callback once with the latest state. if (thisChangeNum !== this._nextNotifification) { return; } for (let subscriber of this._subscribers) { try { subscriber.stateChangeCallback(this.getState()); } catch (ex) { console.error(ex); } } } /** * Subscribe the object to state changes notifications via a `stateChangeCallback` method. * * @param {Object} component to receive state change callbacks via a `stateChangeCallback` method. * If the component is already subscribed, do nothing. */ subscribe(component) { if (this._subscribers.has(component)) { return; } this._subscribers.add(component); } /** * @param {Object} component to stop receiving state change callbacks. */ unsubscribe(component) { this._subscribers.delete(component); } }