Command Pattern in JavaScript: A Complete Guide

This article provides a comprehensive overview of the Command design pattern and demonstrates how to structure and implement it using JavaScript. You will learn the core concepts behind the pattern, examine its primary components—the Command, Receiver, Invoker, and Client—and see a practical code implementation featuring execute and undo operations.

What is the Command Pattern?

The Command Pattern is a behavioral design pattern that encapsulates a request as an object. By converting a request into a standalone object containing all the details about the operation, the pattern decouples the object that invokes the operation from the object that actually knows how to perform it.

This separation of concerns makes it easy to pass operations as method arguments, queue or log operations, schedule tasks, and implement features like undo and redo.

Key Components of the Command Pattern

The Command pattern relies on four main components:

  1. Receiver: The object that contains the core business logic and performs the actual work.
  2. Command: An object or interface that defines the execution method (commonly execute() and optionally undo()).
  3. Concrete Command: Implements the Command interface and binds an action to the Receiver by calling the corresponding methods on the Receiver.
  4. Invoker: The object that holds the command and triggers its execution (e.g., a button, a scheduler, or a command manager).
  5. Client: The code that configures the application by creating receivers, instantiating commands, and assigning them to invokers.

Structuring the Command Pattern in JavaScript

In modern JavaScript, the Command pattern can be structured using ES6 classes. Below is a step-by-step implementation of a simple calculator that supports both executing commands and undoing them.

1. The Receiver

The Receiver performs the actual computation.

class Calculator {
  constructor() {
    this.value = 0;
  }

  add(value) {
    this.value += value;
    console.log(`Current Value: ${this.value}`);
  }

  subtract(value) {
    this.value -= value;
    console.log(`Current Value: ${this.value}`);
  }
}

2. The Concrete Commands

Concrete commands encapsulate the action and hold a reference to the receiver.

class AddCommand {
  constructor(calculator, valueToAdd) {
    this.calculator = calculator;
    this.valueToAdd = valueToAdd;
  }

  execute() {
    this.calculator.add(this.valueToAdd);
  }

  undo() {
    this.calculator.subtract(this.valueToAdd);
  }
}

class SubtractCommand {
  constructor(calculator, valueToSubtract) {
    this.calculator = calculator;
    this.valueToSubtract = valueToSubtract;
  }

  execute() {
    this.calculator.subtract(this.valueToSubtract);
  }

  undo() {
    this.calculator.add(this.valueToSubtract);
  }
}

3. The Invoker

The Invoker executes the commands and maintains a history to support undo operations.

class CalculatorInvoker {
  constructor() {
    this.history = [];
  }

  executeCommand(command) {
    command.execute();
    this.history.push(command);
  }

  undo() {
    const command = this.history.pop();
    if (command) {
      console.log('Undoing last operation...');
      command.undo();
    } else {
      console.log('No operations to undo.');
    }
  }
}

4. The Client Code

The Client ties the components together by instantiating the receiver, the invoker, and executing specific commands.

// Initialize components
const calculator = new Calculator();
const invoker = new CalculatorInvoker();

// Create commands
const addTen = new AddCommand(calculator, 10);
const subtractFive = new SubtractCommand(calculator, 5);

// Execute commands
invoker.executeCommand(addTen);        // Current Value: 10
invoker.executeCommand(subtractFive);  // Current Value: 5

// Undo operations
invoker.undo();                        // Undoing last operation... Current Value: 10
invoker.undo();                        // Undoing last operation... Current Value: 0

When to Use the Command Pattern