Simple Dispatcher Patterns: Clean, Fast Message Delivery

From Zero to Working: Implementing a Simple Dispatcher in Minutes

A simple dispatcher routes events or tasks from producers to one or more consumers with minimal overhead. This article walks you from zero to a working implementation in minutes, covering design, a clear step-by-step build, and a small example you can run immediately.

What a dispatcher does

  • Purpose: Accept messages (events, jobs, or commands) and deliver them to registered handlers.
  • Key features: subscribe/unsubscribe, dispatching, and optional filtering or prioritization.
  • Assumptions: single-process, in-memory dispatcher for clarity and speed.

Design decisions (reasonable defaults)

  • Use an in-memory map of topics → list of handlers.
  • Handlers are simple functions that accept a message.
  • Dispatch synchronously by default; provide an easy async option.
  • Keep the API minimal: subscribe(topic, handler), unsubscribe(topic, handler), dispatch(topic, message).

Implementation (JavaScript — Node.js / browser)

Code below is a compact, well-documented implementation you can paste into a file and run with Node.js or include in a front-end project.

javascript

// simple-dispatcher.js class SimpleDispatcher { constructor() { this.topics = new Map(); // topic -> Set of handlers } subscribe(topic, handler) { if (!this.topics.has(topic)) this.topics.set(topic, new Set()); this.topics.get(topic).add(handler); // return unsubscribe helper return () => this.unsubscribe(topic, handler); } unsubscribe(topic, handler) { const set = this.topics.get(topic); if (!set) return false; set.delete(handler); if (set.size === 0) this.topics.delete(topic); return true; } dispatch(topic, message, { async = false } = {}) { const set = this.topics.get(topic); if (!set) return 0; const handlers = Array.from(set); if (async) { handlers.forEach(h => setImmediate(() => { try { h(message); } catch(e) { console.error(e); } })); } else { for (const h of handlers) { try { h(message); } catch (e) { console.error(e); } } } return handlers.length; } } module.exports = SimpleDispatcher;

Quick example (run immediately)

Create a file named test-dispatcher.js next to the implementation and run node test-dispatcher.js.

javascript

const SimpleDispatcher = require(’./simple-dispatcher’); const dispatcher = new SimpleDispatcher(); function logger(msg) { console.log(‘logger got:’, msg); } function uppercaseHandler(msg) { console.log(‘upper:’, String(msg).toUpperCase()); } dispatcher.subscribe(‘chat’, logger); const unsub = dispatcher.subscribe(‘chat’, uppercaseHandler); console.log(‘Dispatch count:’, dispatcher.dispatch(‘chat’, ‘hello world’)); // 2 unsub(); // remove uppercaseHandler dispatcher.dispatch(‘chat’, ‘second message’); // 1 dispatcher.subscribe(‘bg’, m => console.log(‘bg async:’, m)); dispatcher.dispatch(‘bg’, ‘async message’, { async: true }); // runs async

Extensions & improvements

  • Add wildcard topics (e.g., “user.*”) with pattern matching.
  • Support handler priority or one-shot handlers.
  • Persist messages or add a retry/backoff mechanism.
  • Integrate with worker threads or a message queue for cross-process dispatching.

When to use this

  • In-app event routing (UI events, simple job orchestration).
  • Prototyping systems before adopting heavier message brokers.
  • Lightweight pub/sub within a single process.

Summary

This minimal dispatcher gives you a clear API and immediate utility. It’s synchronous by default for predictability and easily switched to async. Extend with patterns, persistence, or inter-process transport as your needs grow.

Comments

Leave a Reply