Skip to content

[bug] fix(store): async batch() freezes application state updates during await #3244

Description

@Unnati1007

Which package?

@termuijs/store

What happened?

The batch() function incorrectly attempts to span asynchronous functions, freezing the entire application's state updates during await.

When you pass an async function to batch(async () => { ... }), the batch implementation intercepts the returned Promise and delays decrementing _batchDepth until the Promise resolves.

However, JavaScript is single-threaded and batch relies on a global _batchDepth counter. While the async batch function is suspended (e.g., waiting for an await fetch() or a timeout), _batchDepth remains > 0.
Because of this, any unrelated store updates happening anywhere else in the application (like user input handlers, background timers, or incoming network events) will erroneously think they are part of the active batch.

Their state changes will be buffered into the internal _batchStores map, and their listeners will not be notified until the original async batch finally resolves. This causes the UI to completely freeze and swallow updates across the entire application for the duration of any awaited promise inside a batch().

batch (much like React's unstable_batchedUpdates) should only batch synchronous execution. It should not try to await returned promises.

Steps to reproduce

import { createStore, batch } from '@termuijs/store';

const store = createStore({ count: 0 });

async function sleep(ms: number) {
    return new Promise(r => setTimeout(r, ms));
}

let updates = 0;
store.subscribe(() => updates++);

// Start a batch with an async function
batch(async () => {
    store.setState({ count: 1 });
    // _batchDepth stays > 0 during this await!
    await sleep(500); 
    store.setState({ count: 2 });
});

// While the batch is sleeping, another unrelated part of the app updates the state
setTimeout(() => {
    store.setState({ count: 99 });
    console.log("Unrelated update completed. Current state:", store.getState());
    console.log("Did listeners fire for the unrelated update?", updates > 0 ? "YES" : "NO (BUG!)");
}, 100);

Environment

  • Bun version: 1.1.20
  • OS: Windows 11
  • Terminal emulator: PowerShell
  • TermUI version: main

Screenshots or terminal output

Unrelated update completed. Current state: { count: 99 }
Did listeners fire for the unrelated update? NO (BUG!)

GSSoC contributor?

  • Yes. You contribute under GSSoC 2026.
  • No.

Metadata

Metadata

Assignees

Labels

assignedIssue claimed by a contributor.type:bug+10 pts. Bug fix.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions