DMarlon Studio

Lesson 20 · 20 of 20

Project: an order queue · JavaScript

Final JavaScript project: an order queue that combines classes with private fields, EventEmitter events and async/await processing.

JavaScript · Project: an order queue in DMarlon Studio: the example's code and its output in the terminal

This project brings much of the course together in an order queue. OrderQueue is a class that extends EventEmitter, keeps its state in private fields (#pending and #stock) and processes orders asynchronously. The constructor copies the inventory with { ...stock }, so no outside code can change it.

add() puts the order in the queue and emits queued. The class never writes to the console: it only announces things through events, and the outside code decides what to do with each one (here, print a line; in a real app, update the screen or send an email).

run() is an async method: it takes orders one by one with shift(), waits with await delay(...) to simulate packing, and checks the stock. If there is not enough, it emits failed with the reason and moves on with continue; otherwise it deducts the stock, adds the amount and emits packed. Once the queue is empty, it emits idle with the revenue and the final inventory.

Orders are built from an array of tuples using destructuring, and because run() returns a promise, then prints the closing message once everything is done. This same structure (a stateful service with events and async work) is what you will find in a shop built with DMarlon Studio's Electron, React or Next.js targets.

Code · main.js

const EventEmitter = require("events");const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));// An order queue: private state, events and asynchronous work in one class.class OrderQueue extends EventEmitter {  #pending = [];  #stock;  constructor(stock) { super(); this.#stock = { ...stock }; }  add(order) {    this.#pending.push(order);    this.emit("queued", order);  }  async run() {    let revenue = 0;    while (this.#pending.length > 0) {      const order = this.#pending.shift();      await delay(10 * order.qty); // pretend to pack the parcel      const have = this.#stock[order.item] ?? 0;      if (have < order.qty) { this.emit("failed", order, `only ${have} ${order.item} left`); continue; }      this.#stock[order.item] = have - order.qty;      revenue += order.qty * order.price;      this.emit("packed", order);    }    this.emit("idle", revenue, this.#stock);  }}const queue = new OrderQueue({ book: 5, lamp: 1 });queue.on("queued", (o) => console.log(`queued #${o.id}: ${o.qty} x ${o.item}`));queue.on("packed", (o) => console.log(`packed #${o.id}`));queue.on("failed", (o, reason) => console.log(`failed #${o.id}: ${reason}`));queue.on("idle", (total, stock) => console.log(`idle: ${total} EUR, stock ${JSON.stringify(stock)}`));[["book", 2, 15], ["lamp", 2, 40], ["book", 3, 15]].forEach(([item, qty, price], i) => queue.add({ id: i + 1, item, qty, price }));queue.run().then(() => console.log("All orders processed"));

Output · node main.js

queued #1: 2 x book
queued #2: 2 x lamp
queued #3: 3 x book
packed #1
failed #2: only 1 lamp left
packed #3
idle: 75 EUR, stock {"book":0,"lamp":1}
All orders processed

Key points

  • Private fields protect the queue and the inventory.
  • Events separate the logic from what is done with each result.
  • An async method processes the queue in order with await.
  • A failing order does not stop the rest of the queue.

Exercise

Add a cancel(id) method that removes a pending order and emits cancelled, and change run() so an out-of-stock order goes back to the end of the queue once before it is marked as failed.