Skip to content

Commit

Permalink
fix(store): robust flush batch (#2871)
Browse files Browse the repository at this point in the history
* debug batching

* process all batch functions even on error

* Update src/vanilla/store.ts

Co-authored-by: Daishi Kato <[email protected]>

---------

Co-authored-by: Daishi Kato <[email protected]>
  • Loading branch information
dmaskasky and dai-shi authored Dec 18, 2024
1 parent ee43134 commit 8c37913
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 11 deletions.
19 changes: 8 additions & 11 deletions src/vanilla/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ const registerBatchAtom = (
if (!batch.D.has(atom)) {
batch.D.set(atom, new Set())
addBatchFuncMedium(batch, () => {
atomState.m?.l.forEach((listener) => listener())
atomState.m?.l.forEach((listener) => addBatchFuncMedium(batch, listener))
})
}
}
Expand All @@ -220,12 +220,6 @@ const addBatchAtomDependent = (
const getBatchAtomDependents = (batch: Batch, atom: AnyAtom) =>
batch.D.get(atom)

const copySetAndClear = <T>(origSet: Set<T>): Set<T> => {
const newSet = new Set(origSet)
origSet.clear()
return newSet
}

const flushBatch = (batch: Batch) => {
let error: AnyError
let hasError = false
Expand All @@ -239,11 +233,14 @@ const flushBatch = (batch: Batch) => {
}
}
}
while (batch.M.size || batch.L.size) {
while (batch.H.size || batch.M.size || batch.L.size) {
batch.D.clear()
copySetAndClear(batch.H).forEach(call)
copySetAndClear(batch.M).forEach(call)
copySetAndClear(batch.L).forEach(call)
batch.H.forEach(call)
batch.H.clear()
batch.M.forEach(call)
batch.M.clear()
batch.L.forEach(call)
batch.L.clear()
}
if (hasError) {
throw error
Expand Down
22 changes: 22 additions & 0 deletions tests/vanilla/store.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -983,3 +983,25 @@ it('mounted atom should be recomputed eagerly', () => {
store.set(a, 1)
expect(result).toEqual(['bRead', 'aCallback', 'bCallback'])
})

it('should process all atom listeners even if some of them throw errors', () => {
const store = createStore()
const a = atom(0)
const listenerA = vi.fn()
const listenerB = vi.fn(() => {
throw new Error('error')
})
const listenerC = vi.fn()

store.sub(a, listenerA)
store.sub(a, listenerB)
store.sub(a, listenerC)
try {
store.set(a, 1)
} catch {
// expect empty
}
expect(listenerA).toHaveBeenCalledTimes(1)
expect(listenerB).toHaveBeenCalledTimes(1)
expect(listenerC).toHaveBeenCalledTimes(1)
})

0 comments on commit 8c37913

Please sign in to comment.