#2 Model a task queue with XState
Published on
Implement a task queue in XState by processing tasks individually, queuing new tasks while busy, automatically triggering the next task with eventless transitions, and simplifying the design by globally handling task addition across all states.
Hey there!
Let’s say you are developing an application that needs to execute some tasks sequentially, like file uploads.
You may have been using XState in your application for a long time and immediately think about XState to implement this feature. Or you may have been thinking about using XState for the first time, in which case this is a great reason to start XStating.
The specifications of the task queue are:
- Process tasks individually and start a new one as soon as the current one ends.
- Add tasks to the queue at any time during the flow.
permalinkProcess incoming tasks directly
The state machine starts from the Idle
state.
When a task is received in the Idle
state through the Add task to queue
event, store the task in the context of the machine thanks to the Add task to queue in context
action and then target the Processing
state.
Process the task with the Process task
service invoked in the Processing
state. This service picks the first task from the queue and processes it.
Once the task is done, return to the Idle
state and delete the processed task from the queue.
permalinkEnqueue tasks in the context when busy
Even while processing a task, we want to queue new tasks we receive to process them later when the machine isn’t busy anymore.
We need to listen to the Add task to queue
event in the Processing
state and add the task to the queue when we receive this event. For that, we will use a self-transition. A self-transition is a transition that targets the source state itself. The state machine remains in its state with such a transition.
By default, services are stopped when exiting a state. In our case, it means that the processing of the current task would be aborted.
However, self-transitions don’t re-enter by default. That way, we can avoid interrupting the current processing and stay in the Processing
state. The transition will still be taken, and the Assign task to queue in context
action will be called, but the current process won’t be aborted.
You can opt-in to make a self-transition re-enter. A self-transition that does re-enter is useful when you want to restart a service. This is a powerful feature that comes cheaply with state machines.
permalinkAutomatically process the next task when processing finishes
Returning to the Idle
state should automatically trigger the processing of the remaining tasks in the queue.
This can be achieved thanks to an eventless transition: when the Idle
state is entered, and each time the machine receives an event while in this state, the condition Task available
is evaluated, and the transition may be taken if the guard evaluates to true
.
We no longer target the Processing
state when receiving an Add task to queue
event in the Idle
state; the eventless transition does it automatically for us. Pretty neat!
permalinkSimplify the state machine
The Add task to queue
event can be factorized by listening to it globally on the root state. The machine will add tasks to the queue, whatever the current state is.
When reaching the Idle
state, the state machine checks for available tasks to process or waits for one. The eventless transition is the key to the magic of this state machine!
permalinkWrap up
Working on small machines like this example is an excellent way to exercise your state machine modeling practice.
If you’d like to go one step further, you can implement a variant of this task queue that can share work amongst several workers. Several ways to implement it, and many details can be considered.
For instance, in which order should tasks be distributed to free workers? Should workers always be tried sequentially, or should there be some round-robin mechanism?
Feel free to go with your solution! And please share it with me.
In the next post, you will learn how XState Typegen works!
Best,
Baptiste