DMarlon Studio

Lección 20 · 20 de 20

Proyecto: cola de pedidos · JavaScript

Proyecto final en JavaScript: una cola de pedidos que combina clases con campos privados, eventos de EventEmitter y procesamiento con async/await.

JavaScript · Proyecto: cola de pedidos en DMarlon Studio: el código del ejemplo y su salida en la terminal

Este proyecto reúne buena parte del curso en una cola de pedidos. OrderQueue es una clase que hereda de EventEmitter, guarda su estado en campos privados (#pending y #stock) y procesa los pedidos de forma asíncrona. El constructor copia el inventario con { ...stock }, de modo que nadie de fuera pueda cambiarlo.

add() mete el pedido en la cola y emite queued. La clase no escribe nada en la consola: solo avisa con eventos, y es el código de fuera el que decide qué hacer con cada aviso (aquí, escribir una línea; en una aplicación real, actualizar la pantalla o enviar un correo).

run() es un método async: saca los pedidos uno a uno con shift(), espera con await delay(...) para simular el empaquetado y comprueba las existencias. Si no hay bastantes, emite failed con el motivo y pasa al siguiente con continue; si las hay, las descuenta, suma el importe y emite packed. Al vaciarse la cola, emite idle con la facturación y el inventario final.

Los pedidos se crean a partir de un array de tuplas con desestructuración, y como run() devuelve una promesa, then escribe el mensaje final cuando todo ha terminado. Esta misma estructura (un servicio con estado, eventos y trabajo asíncrono) es la que encontrará en una tienda hecha con los destinos Electron, React o Next.js de DMarlon Studio.

Código · 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"));

Salida · 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

Para recordar

  • Los campos privados protegen la cola y el inventario.
  • Los eventos separan la lógica de lo que se hace con cada resultado.
  • Un método async procesa la cola en orden con await.
  • Un pedido que falla no detiene el resto de la cola.

Ejercicio

Añada un método cancel(id) que quite un pedido pendiente y emita cancelled, y cambie run() para que un pedido sin existencias vuelva al final de la cola una sola vez antes de darse por fallido.