makeSafeRefluxStore.ts 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
  1. export interface SafeStoreDefinition extends Reflux.StoreDefinition {
  2. unsubscribeListeners: Reflux.Subscription[];
  3. /**
  4. * Teardown a store and all it's listeners
  5. */
  6. teardown?(): void;
  7. }
  8. export interface SafeRefluxStore extends Reflux.Store {
  9. teardown(): void;
  10. unsubscribeListeners: Reflux.Subscription[];
  11. }
  12. // XXX: Teardown will call stop on any listener subscriptions
  13. // that have been started as a consequence of listenTo calls.
  14. export function cleanupActiveRefluxSubscriptions(
  15. subscriptions: Array<Reflux.Subscription>
  16. ) {
  17. while (subscriptions.length > 0) {
  18. const unsubscribeListener = subscriptions.pop();
  19. if (unsubscribeListener !== undefined && 'stop' in unsubscribeListener) {
  20. unsubscribeListener.stop();
  21. continue;
  22. }
  23. const stringifiedListenerType = JSON.stringify(unsubscribeListener);
  24. throw new Error(
  25. `Attempting to call ${stringifiedListenerType}. Unsubscribe listeners should only include function calls`
  26. );
  27. }
  28. }
  29. // XXX: Reflux will implicitly call .init on a store when it is created
  30. // (see node_modules/reflux/dist/reflux.js L768), and because our stores
  31. // often subscribe to .listenTo inside init calls without storing the
  32. // cleanup functions, our subscriptions are never cleaned up. This is fine
  33. // for production env where stores are only init once, but causes memory
  34. // leaks in tests when we call store.init inside a beforeEach hook.
  35. export function makeSafeRefluxStore<
  36. T extends SafeStoreDefinition | Reflux.StoreDefinition
  37. >(store: T): SafeRefluxStore & T {
  38. // Allow for a store to pass it's own array of unsubscribeListeners, else initialize one
  39. const safeStore = store as unknown as SafeRefluxStore & T;
  40. safeStore.unsubscribeListeners = Array.isArray(safeStore.unsubscribeListeners)
  41. ? safeStore.unsubscribeListeners
  42. : [];
  43. // Cleanup any subscriptions that were stored
  44. function teardown(this: SafeRefluxStore) {
  45. cleanupActiveRefluxSubscriptions(this.unsubscribeListeners);
  46. }
  47. // We allow for some stores to implement their own teardown mechanism
  48. // in case other listeners are attached to the store (eg. browser history listeners etc)
  49. if (!safeStore.teardown) {
  50. safeStore.teardown = teardown.bind(safeStore);
  51. }
  52. return safeStore;
  53. }