Ver Fonte

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
cngu há 5 anos atrás
pai
commit
e012653330
2 ficheiros alterados com 47 adições e 1 exclusões
  1. 5 1
      src/store.js
  2. 42 0
      test/unit/store.spec.js

+ 5 - 1
src/store.js

@@ -101,7 +101,10 @@ export class Store {
         handler(payload)
       })
     })
-    this._subscribers.forEach(sub => sub(mutation, this.state))
+
+    this._subscribers
+      .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
+      .forEach(sub => sub(mutation, this.state))
 
     if (
       process.env.NODE_ENV !== 'production' &&
@@ -132,6 +135,7 @@ export class Store {
 
     try {
       this._actionSubscribers
+        .slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
         .filter(sub => sub.before)
         .forEach(sub => sub.before(action, this.state))
     } catch (e) {

+ 42 - 0
test/unit/store.spec.js

@@ -320,6 +320,48 @@ 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)
+  })
+
+  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', () => {