diff --git a/src/vanilla/store2.ts b/src/vanilla/store2.ts index 2d2e3db3ce..d83f7bd069 100644 --- a/src/vanilla/store2.ts +++ b/src/vanilla/store2.ts @@ -285,6 +285,7 @@ export const createStore = (): Store => { if (pendingPromise) { if (pendingPromise !== valueOrPromise) { pendingPromise[CONTINUE_PROMISE](valueOrPromise, abortPromise) + ++atomState.n } } else { const continuablePromise = createContinuablePromise( @@ -499,8 +500,7 @@ export const createStore = (): Store => { for (let i = topsortedAtoms.length - 1; i >= 0; --i) { const a = topsortedAtoms[i]! const aState = getAtomState(a) - const hasPrevValue = 'v' in aState - const prevValue = aState.v + const prevEpochNumber = aState.n let hasChangedDeps = false for (const dep of aState.d.keys()) { if (dep !== a && changedAtoms.has(dep)) { @@ -511,7 +511,7 @@ export const createStore = (): Store => { if (hasChangedDeps) { readAtomState(pending, a, isMarked) mountDependencies(pending, a, aState) - if (!hasPrevValue || !Object.is(prevValue, aState.v)) { + if (prevEpochNumber !== aState.n) { addPendingAtom(pending, a, aState) changedAtoms.add(a) } diff --git a/tests/vanilla/dependency.test.tsx b/tests/vanilla/dependency.test.tsx index 7a97794d7f..0529639f0c 100644 --- a/tests/vanilla/dependency.test.tsx +++ b/tests/vanilla/dependency.test.tsx @@ -184,3 +184,85 @@ it('should not provide stale values to conditional dependents', () => { store.set(hasFilterAtom, true) expect(store.get(stageAtom), 'should update').toBe('is-empty') }) + +it('settles never resolving async derivations with deps picked up sync', async () => { + const resolve: ((value: number) => void)[] = [] + + const syncAtom = atom({ + promise: new Promise((r) => resolve.push(r)), + }) + + const asyncAtom = atom(async (get) => { + return await get(syncAtom).promise + }) + + const store = createStore() + + let sub = 0 + const values: unknown[] = [] + store.get(asyncAtom).then((value) => values.push(value)) + + store.sub(asyncAtom, () => { + sub++ + store.get(asyncAtom).then((value) => values.push(value)) + }) + + await new Promise((r) => setTimeout(r)) + + store.set(syncAtom, { + promise: new Promise((r) => resolve.push(r)), + }) + + await new Promise((r) => setTimeout(r)) + + resolve[1]?.(1) + + await new Promise((r) => setTimeout(r)) + + expect(values).toEqual([1, 1]) + expect(sub).toBe(1) +}) + +it.skipIf(!import.meta.env?.USE_STORE2)( + 'settles never resolving async derivations with deps picked up async', + async () => { + const resolve: ((value: number) => void)[] = [] + + const syncAtom = atom({ + promise: new Promise((r) => resolve.push(r)), + }) + + const asyncAtom = atom(async (get) => { + // we want to pick up `syncAtom` as an async dep + await Promise.resolve() + + return await get(syncAtom).promise + }) + + const store = createStore() + + let sub = 0 + const values: unknown[] = [] + store.get(asyncAtom).then((value) => values.push(value)) + + store.sub(asyncAtom, () => { + sub++ + store.get(asyncAtom).then((value) => values.push(value)) + }) + + await new Promise((r) => setTimeout(r)) + + store.set(syncAtom, { + promise: new Promise((r) => resolve.push(r)), + }) + + await new Promise((r) => setTimeout(r)) + + resolve[1]?.(1) + + await new Promise((r) => setTimeout(r)) + + expect(values).toEqual([1, 1]) + expect(sub).toBe(1) + }, +)