Skip to content

Commit 60b9c6d

Browse files
committed
fix: async subscription do not run when mutate state synchronously after patch
1 parent 2071db2 commit 60b9c6d

File tree

2 files changed

+55
-25
lines changed

2 files changed

+55
-25
lines changed

packages/pinia/__tests__/subscriptions.spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,5 +353,35 @@ describe('Subscriptions', () => {
353353
expect(spy1).toHaveBeenCalledTimes(1)
354354
expect(spy2).toHaveBeenCalledTimes(2)
355355
})
356+
357+
it('debugger events should not be array when subscription is not trigger by patch', () => {
358+
const store = useStore()
359+
store.$subscribe(
360+
({ type, events }) => {
361+
if (type === MutationType.direct) {
362+
expect(Array.isArray(events)).toBe(false)
363+
}
364+
},
365+
{ flush: 'sync' }
366+
)
367+
store.$patch({ user: 'Edu' })
368+
store.user = 'a'
369+
})
370+
371+
it('should trigger subscription when mutate state synchronously after patch', async () => {
372+
const store = useStore()
373+
const spy1 = vi.fn()
374+
const spy2 = vi.fn()
375+
const spy3 = vi.fn()
376+
store.$subscribe(spy1, { flush: 'sync' })
377+
store.$subscribe(spy2, { flush: 'pre' })
378+
store.$subscribe(spy3, { flush: 'post' })
379+
store.$patch({ user: 'Edu' })
380+
store.user = 'a'
381+
expect(spy1).toHaveBeenCalledTimes(2)
382+
await nextTick()
383+
expect(spy2).toHaveBeenCalledTimes(2)
384+
expect(spy3).toHaveBeenCalledTimes(2)
385+
})
356386
})
357387
})

packages/pinia/src/store.ts

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import {
2121
ref,
2222
set,
2323
del,
24-
nextTick,
2524
isVue2,
2625
} from 'vue-demi'
2726
import {
@@ -256,7 +255,7 @@ function createSetupStore<
256255
if (isListening) {
257256
debuggerEvents = event
258257
// avoid triggering this while the store is being built and the state is being set in pinia
259-
} else if (isListening == false && !store._hotUpdating) {
258+
} else if (isListening === false && !store._hotUpdating) {
260259
// let patch send all the events together later
261260
/* istanbul ignore else */
262261
if (Array.isArray(debuggerEvents)) {
@@ -271,8 +270,8 @@ function createSetupStore<
271270
}
272271

273272
// internal state
274-
let isListening: boolean // set to true at the end
275-
let isSyncListening: boolean // set to true at the end
273+
let isListening = false // set to true at the end
274+
let shouldTrigger = false // The initial value does not matter, and no need to set to true at the end
276275
let subscriptions: SubscriptionCallback<S>[] = []
277276
let actionSubscriptions: StoreOnActionListener<Id, S, G, A>[] = []
278277
let debuggerEvents: DebuggerEvent[] | DebuggerEvent
@@ -291,9 +290,6 @@ function createSetupStore<
291290

292291
const hotState = ref({} as S)
293292

294-
// avoid triggering too many listeners
295-
// https://github.com/vuejs/pinia/issues/1129
296-
let activeListener: Symbol | undefined
297293
function $patch(stateMutation: (state: UnwrapRef<S>) => void): void
298294
function $patch(partialState: _DeepPartial<UnwrapRef<S>>): void
299295
function $patch(
@@ -302,7 +298,7 @@ function createSetupStore<
302298
| ((state: UnwrapRef<S>) => void)
303299
): void {
304300
let subscriptionMutation: SubscriptionCallbackMutation<S>
305-
isListening = isSyncListening = false
301+
isListening = false
306302
// reset the debugger events since patches are sync
307303
/* istanbul ignore else */
308304
if (__DEV__) {
@@ -324,13 +320,7 @@ function createSetupStore<
324320
events: debuggerEvents as DebuggerEvent[],
325321
}
326322
}
327-
const myListenerId = (activeListener = Symbol())
328-
nextTick().then(() => {
329-
if (activeListener === myListenerId) {
330-
isListening = true
331-
}
332-
})
333-
isSyncListening = true
323+
isListening = true
334324
// because we paused the watcher, we need to manually call the subscriptions
335325
triggerSubscriptions(
336326
subscriptions,
@@ -454,11 +444,19 @@ function createSetupStore<
454444
options.detached,
455445
() => stopWatcher()
456446
)
457-
const stopWatcher = scope.run(() =>
458-
watch(
447+
const stopWatcher = scope.run(() => {
448+
const stop1 = watch(
449+
() => pinia.state.value[$id],
450+
() => {
451+
shouldTrigger = isListening
452+
},
453+
{ deep: true, flush: 'sync' }
454+
)
455+
456+
const stop2 = watch(
459457
() => pinia.state.value[$id] as UnwrapRef<S>,
460458
(state) => {
461-
if (options.flush === 'sync' ? isSyncListening : isListening) {
459+
if (shouldTrigger) {
462460
callback(
463461
{
464462
storeId: $id,
@@ -471,7 +469,14 @@ function createSetupStore<
471469
},
472470
assign({}, $subscribeOptions, options)
473471
)
474-
)!
472+
473+
const stop = () => {
474+
stop1()
475+
stop2()
476+
}
477+
478+
return stop
479+
})!
475480

476481
return removeSubscription
477482
},
@@ -647,12 +652,8 @@ function createSetupStore<
647652

648653
// avoid devtools logging this as a mutation
649654
isListening = false
650-
isSyncListening = false
651655
pinia.state.value[$id] = toRef(newStore._hmrPayload, 'hotState')
652-
isSyncListening = true
653-
nextTick().then(() => {
654-
isListening = true
655-
})
656+
isListening = true
656657

657658
for (const actionName in newStore._hmrPayload.actions) {
658659
const actionFn: _Method = newStore[actionName]
@@ -779,7 +780,6 @@ function createSetupStore<
779780
}
780781

781782
isListening = true
782-
isSyncListening = true
783783
return store
784784
}
785785

0 commit comments

Comments
 (0)