I implemented a simple queuing system for my Node.JS app and wanted a critique on it's structure.
const TPS = 20;
const Queue = {
counter: 1,
items: {},
/**
* Add an item to the queue, with the given func to call
* @param {Function} func
* @param {Boolean} repeating
* @return {Number}
*/
add(func, repeating = false) {
const id = this.counter++;
this.items[id] = {func, repeating, id};
return id;
},
/**
* Remove an item from the queue with the given id
* @param {Number} id
*/
remove(id) {
if (this.items.hasOwnProperty(id)) {
delete this.items[id];
}
},
/**
* Process items in the queue
*/
process() {
for (let id in this.items) {
// Prevent this item from being processed again
if (!this.items.hasOwnProperty(id) || this.items[id].processing) {
continue;
}
// Delete this item when it's scheduled for deletion
if (this.items[id].scheduledForDeletion) {
delete this.items[id];
continue;
}
// Let the queue know this item is being processed and
// it's scheduled deletion status
this.items[id].processing = true;
this.items[id].scheduledForDeletion = !this.items[id].repeating;
// Don't wait for item's promise to resolve, since this
// will create a backlog on the queue
(async () => {
try {
await this.items[id].func.call(null);
} catch (err) {
// TODO: Handle errors.
console.error(err);
}
this.items[id].processing = false;
})();
}
}
};
(function tick() {
setTimeout(tick, 1000 / TPS);
Queue.process();
})();
This is an example of how it's implemented.
// Add three items to the queue: 1 normal, 1 async and 1 repeating
Queue.add(() => console.info(`[tick] -> ${Date.now()}`));
Queue.add(async () => setTimeout(() => console.info(`[async] -> ${Date.now()}`), 100));
const timeLoop = Queue.add(() => console.info(`[loop] time (loop) -> ${Date.now()}`), true);
// Remove the looping item from the queue
setTimeout(() => Queue.remove(timeLoop), 500);
The idea is to have this run when the server starts and continually process the queue items. Queue is in it's own file and exported. I import this file into my controllers and call (for example) Queue.add('function to add user to DB and send out email')
.
1 Answer 1
The structure looks fine. It is quite succinct and makes good use of const
and let
where appropriate.
To adhere to the D.R.Y. principle, process()
can and should utilize remove()
to remove items from the queue.
I considered suggesting that arguments be accepted with each function but that can be achieved with partially bound functions.
I also considered suggesting you consider using a class, since ES-6 featured can be utilized, but then you would either need to instantiate a queue once or else make all methods static.
I would suggest you consider accepting an error handler callback for each function. That way, instead of writing all errors to the console, the caller could add an appropriate handler.
Explore related questions
See similar questions with these tags.
process
; andasync
atQueue.add(async () => setTimeout(() => console.info(`[async] -> ${Date.now()}`), 100))
where noPromise
is included within or returned from the function passed toQueue.add()
? IsQueue.add()
andprocess
expected to handle both asynchronous and synchronous functions as parameters? \$\endgroup\$await
keyword needs to be inside anasync
body, but I think it might be better to move it to the process function definition likeasync process()
. TheQueue.add
calls at the end of my code are just examples to show you that you can add async items to the queue. And actuallyasync
returns a promise by default so technically they are returning promises. Finally, yes, the process method handles both sync and async items. \$\endgroup\$async () => setTimeout(() => console.info(`[async] -> ${Date.now()}`), 100)
return any value? See Why is value undefined at .then() chained to Promise? \$\endgroup\$