This time, I didn’t pick this pattern from the awful book of Javapocalypse. It’s something I discovered a few years ago when a colleague presented to me RabbitMQ. This technology is a software managing a lot of requests among micro-services by using a message queue. And it’s something very simple but now really important in cloud programming.
In programming, there are many queues: message queue, job queue, task queue, priority queue, command queue, etc. Let’s be simple, and talk about the queue like something calling your functions in the desired order. I’ll present to you the fundamental queue and some of its variations of the base in this article.
The basic version
The queue is a simple structure with two properties: the tasks and a boolean to know if the queue is running. There’s a function to add a new task to the queue and another function to run the queue. So we can easily get this code:
In theory, it works. In 99% of use cases, it works. But this code has a big problem due to the Javascript call stack. Each time you call a function, you push its information on the call stack. By doing this, your program knows what was and how to return to the previous function. That’s one of the main mechanisms of programming languages.
If we execute our code by adding three tasks and running the queue, we get something like this :
The run()
function calls itself while there’s a task in the queue. By doing this, we push the information of run function each time on the call stack while there’s a task in the queue. Is the call stack infinite? Of course not. You can execute this code to check the size of the call stack:
In my case, if I run 7842 tasks, I break the program. To fix this, we have two solutions: node.js Events or a good ol’ while loop. We will use the second solution because it works in Vanilla Javascript too.
So here’s the basic queue which can run for a long time. Maybe you prefer the queue to have an automatic start when a task is added, so let’s move on this code:
Designing tasks
An essential part of using a queue is to design what is your task. This task is generally a function or an object.
In the case of a function, you have to define:
- the parameters used to call your task.
- the way to wait until the end of a task. If it’s a callback, we have to manage the queue to continue the code when the callback is called. If it’s a Promise, we have to use an
await currentTask()
in therun()
.
In the case of an object, you have to define the properties of an object. For example, if I create an HTTPRequestQueue, my object will contain properties like the HTTP method, the URL, the body, the headers, etc.
By using an object, you choose the behavior of your queue due to the impossibility of executing arbitrary code. In other words, using an object is the best way to make a business-specific queue, and using a function is the best way to make a generic queue.
Waiting the end of the queue
The first thing you could need would be to wait until the end of the queue. For example, you want to run a batch of network requests before displaying a success message. In this case, we will use a classic Observer:
I know callbacks are a bit oldish, so you can extend this code by adding a waitingEnd()
function which returns a Promise:
Running tasks in parallel
Executing tasks one-by-one can be a bit slow. Maybe you want to run a set of N tasks in parallel. We must reconsider the queue by adding the possibility to run N times the while
loop. Javascript is a mono-threaded language, so we don’t have to manage concurrency problems like concurrent modifications of a variable. So here we go:
Now, you’re the master of the queue. There are many possible extensions to this structure, like adding events at each end of a task, creating rollbacks when a task failed, or anything useful for you.