Skip to content

Commit e012653

Browse files
cnguktsn
authored andcommitted
fix: Prevent invalidating subscription iterator (#1438)
* Prevent users from invalidating the subscription iterator by synchronously calling unsubscribe * Prevent users from invalidating the action subscription iterator by synchronously calling unsubscribe * Fix commit subscribers argument invocation
1 parent 8fd61c9 commit e012653

File tree

2 files changed

+47
-1
lines changed

2 files changed

+47
-1
lines changed

src/store.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,10 @@ export class Store {
101101
handler(payload)
102102
})
103103
})
104-
this._subscribers.forEach(sub => sub(mutation, this.state))
104+
105+
this._subscribers
106+
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
107+
.forEach(sub => sub(mutation, this.state))
105108

106109
if (
107110
process.env.NODE_ENV !== 'production' &&
@@ -132,6 +135,7 @@ export class Store {
132135

133136
try {
134137
this._actionSubscribers
138+
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
135139
.filter(sub => sub.before)
136140
.forEach(sub => sub.before(action, this.state))
137141
} catch (e) {

test/unit/store.spec.js

+42
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,48 @@ describe('Store', () => {
320320
expect(secondSubscribeSpy.calls.count()).toBe(2)
321321
})
322322

323+
it('subscribe: should handle subscriptions with synchronous unsubscriptions', () => {
324+
const subscribeSpy = jasmine.createSpy()
325+
const testPayload = 2
326+
const store = new Vuex.Store({
327+
state: {},
328+
mutations: {
329+
[TEST]: () => {}
330+
}
331+
})
332+
333+
const unsubscribe = store.subscribe(() => unsubscribe())
334+
store.subscribe(subscribeSpy)
335+
store.commit(TEST, testPayload)
336+
337+
expect(subscribeSpy).toHaveBeenCalledWith(
338+
{ type: TEST, payload: testPayload },
339+
store.state
340+
)
341+
expect(subscribeSpy.calls.count()).toBe(1)
342+
})
343+
344+
it('subscribeAction: should handle subscriptions with synchronous unsubscriptions', () => {
345+
const subscribeSpy = jasmine.createSpy()
346+
const testPayload = 2
347+
const store = new Vuex.Store({
348+
state: {},
349+
actions: {
350+
[TEST]: () => {}
351+
}
352+
})
353+
354+
const unsubscribe = store.subscribeAction(() => unsubscribe())
355+
store.subscribeAction(subscribeSpy)
356+
store.dispatch(TEST, testPayload)
357+
358+
expect(subscribeSpy).toHaveBeenCalledWith(
359+
{ type: TEST, payload: testPayload },
360+
store.state
361+
)
362+
expect(subscribeSpy.calls.count()).toBe(1)
363+
})
364+
323365
// store.watch should only be asserted in non-SSR environment
324366
if (!isSSR) {
325367
it('strict mode: warn mutations outside of handlers', () => {

0 commit comments

Comments
 (0)