From c99619d823021763f9aab15e1a950ee2da582476 Mon Sep 17 00:00:00 2001 From: Chris Nguyen Date: Fri, 2 Nov 2018 10:06:18 -0600 Subject: [PATCH 1/3] Prevent users from invalidating the subscription iterator by synchronously calling unsubscribe --- src/store.js | 2 +- test/unit/store.spec.js | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/store.js b/src/store.js index 4251eaa73..77c5d3cba 100644 --- a/src/store.js +++ b/src/store.js @@ -100,7 +100,7 @@ export class Store { handler(payload) }) }) - this._subscribers.forEach(sub => sub(mutation, this.state)) + this._subscribers.slice().forEach(sub => sub(mutation, this.state)) if ( process.env.NODE_ENV !== 'production' && diff --git a/test/unit/store.spec.js b/test/unit/store.spec.js index 001ee4249..5a86be8a7 100644 --- a/test/unit/store.spec.js +++ b/test/unit/store.spec.js @@ -320,6 +320,27 @@ describe('Store', () => { expect(secondSubscribeSpy.calls.count()).toBe(2) }) + it('subscribe: should handle subscriptions with synchronous unsubscriptions', () => { + const subscribeSpy = jasmine.createSpy() + const testPayload = 2 + const store = new Vuex.Store({ + state: {}, + mutations: { + [TEST]: () => {} + } + }) + + const unsubscribe = store.subscribe(() => unsubscribe()) + store.subscribe(subscribeSpy) + store.commit(TEST, testPayload) + + expect(subscribeSpy).toHaveBeenCalledWith( + { type: TEST, payload: testPayload }, + store.state + ) + expect(subscribeSpy.calls.count()).toBe(1) + }) + // store.watch should only be asserted in non-SSR environment if (!isSSR) { it('strict mode: warn mutations outside of handlers', () => { From e404657d778ecc2267cd4eed79d97c92b49a3523 Mon Sep 17 00:00:00 2001 From: Chris Nguyen Date: Wed, 19 Dec 2018 23:07:13 -0700 Subject: [PATCH 2/3] Prevent users from invalidating the action subscription iterator by synchronously calling unsubscribe --- src/store.js | 10 ++++++++-- test/unit/store.spec.js | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/store.js b/src/store.js index 77c5d3cba..d57880034 100644 --- a/src/store.js +++ b/src/store.js @@ -100,7 +100,8 @@ export class Store { handler(payload) }) }) - this._subscribers.slice().forEach(sub => sub(mutation, this.state)) + + notifySubscribers(this._subscribers, mutation, this.state) if ( process.env.NODE_ENV !== 'production' && @@ -129,7 +130,7 @@ export class Store { return } - this._actionSubscribers.forEach(sub => sub(action, this.state)) + notifySubscribers(this._actionSubscribers, action, this.state) return entry.length > 1 ? Promise.all(entry.map(handler => handler(payload))) @@ -439,6 +440,11 @@ function registerGetter (store, type, rawGetter, local) { } } +function notifySubscribers (subscribers, message, state) { + // create shallow copy to prevent iterator invalidation if users synchronously call unsubscribe + subscribers.slice().forEach(sub => sub(message, state)) +} + function enableStrictMode (store) { store._vm.$watch(function () { return this._data.$$state }, () => { if (process.env.NODE_ENV !== 'production') { diff --git a/test/unit/store.spec.js b/test/unit/store.spec.js index 5a86be8a7..561496e60 100644 --- a/test/unit/store.spec.js +++ b/test/unit/store.spec.js @@ -341,6 +341,27 @@ describe('Store', () => { expect(subscribeSpy.calls.count()).toBe(1) }) + it('subscribeAction: should handle subscriptions with synchronous unsubscriptions', () => { + const subscribeSpy = jasmine.createSpy() + const testPayload = 2 + const store = new Vuex.Store({ + state: {}, + actions: { + [TEST]: () => {} + } + }) + + const unsubscribe = store.subscribeAction(() => unsubscribe()) + store.subscribeAction(subscribeSpy) + store.dispatch(TEST, testPayload) + + expect(subscribeSpy).toHaveBeenCalledWith( + { type: TEST, payload: testPayload }, + store.state + ) + expect(subscribeSpy.calls.count()).toBe(1) + }) + // store.watch should only be asserted in non-SSR environment if (!isSSR) { it('strict mode: warn mutations outside of handlers', () => { From 9cddc8aa689e6cc44093566a6c781c416d1f8ec0 Mon Sep 17 00:00:00 2001 From: Chris Nguyen Date: Tue, 24 Dec 2019 20:37:07 -0700 Subject: [PATCH 3/3] Fix commit subscribers argument invocation --- src/store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store.js b/src/store.js index d6a87a682..8b89d8187 100644 --- a/src/store.js +++ b/src/store.js @@ -104,7 +104,7 @@ export class Store { this._subscribers .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe - .forEach(sub => sub(message, state)) + .forEach(sub => sub(mutation, this.state)) if ( process.env.NODE_ENV !== 'production' &&