Should dispatched transactions be added to a queue?

Having recently experienced a “Calls to EditorView.update are not allowed while an update is in progress” error thrown when a transaction was dispatched while another was in progress (due to being dispatched in response to two closely-timed asynchronous events), I was slightly surprised to find that the dispatch method doesn’t put transactions on a queue for processing.

While it would be easy enough to add a queue in a custom dispatch function, I’m wondering whether that would be a bad idea for any reason, particularly in a way that explains why it isn’t implemented as a queue by default.

When creating a transaction, you are basing it on the current editor state. It would often not make any sense anymore when applied from a different state. Also dispatching transactions is synchronous, so a queue seems needless complexity.

Would it not be correct to say that whenever something asynchronous happens which ends with a call to view.dispatch, there’s always a chance that the view will already be processing an update at that time, which would result in an error being thrown?

The way to write asynchronous stuff that dispatches is to do the async action, and then figure out how it applies to the current state. This may in some cases involve mapping a position known at the start of the action forward through updates (which is why it is convenient to define this kind of stuff as a view plugin).

To give some more concrete examples, the transaction being dispatched could be a user selecting a font size from a menu, or a response from a spelling server containing a list of misspelled words to be highlighted.

These don’t depend on the current state, they’re effects sent directly to view.dispatch, so could, in theory, collide with the processing of another transaction. The only solution I know of so far is to wrap the view.dispatch in a setTimeout so it runs after the current synchronous tasks (which includes any in-progress transaction processing) have finished. Which is ok, but it’s easy to forget.

In your description above, does the view plugin not still need to call view.dispatch?

What is triggering the dispatch here? Are you dispatching from an update method?

Ok, I think I see the flaw in my reasoning - thanks for helping to work through it. It’s because the transaction processing is synchronous, any asynchronous code which calls view.dispatch will be run from the microtask queue and can only happen once the current transaction processing has finished.

In which case it’s only calls to view.dispatch that are triggered by updates within a CodeMirror cycle that we need to worry about, and these can be wrapped in setTimeout (or, perhaps, queueMicrotask).