Command membungkus permintaan (apa yang harus dilakukan + data) ke dalam object. Object itu bisa disimpan, diantri, di-undo, direplay, atau dikirim ke worker/server.
Problem yang diselesaikan:
Kadang kamu butuh lebih dari sekadar "eksekusi sekarang". Kamu butuh:
- Undo/redo — tahu cara membatalkan aksi
- Macro recording — rekam urutan aksi, putar ulang
- Task queue — antri pekerjaan, jalankan nanti oleh worker
- Audit log — simpan riwayat aksi + actor + timestamp
- Retry — kalau gagal, coba lagi aksi yang sama
Semua ini butuh aksi yang bisa dibawa-bawa sebagai data — itulah Command.
Implementasi minimal — undo/redo untuk text editor:
class InsertTextCommand {
constructor(editor, text, position) {
this.editor = editor;
this.text = text;
this.position = position;
}
execute() {
this.editor.insert(this.position, this.text);
}
undo() {
this.editor.delete(this.position, this.text.length);
}
}
class DeleteTextCommand {
constructor(editor, position, length) {
this.editor = editor;
this.position = position;
this.length = length;
this.deletedText = null; // diisi saat execute
}
execute() {
this.deletedText = this.editor.content.slice(this.position, this.position + this.length);
this.editor.delete(this.position, this.length);
}
undo() {
this.editor.insert(this.position, this.deletedText);
}
}
// Invoker / history
class CommandHistory {
constructor() {
this.done = [];
this.undone = [];
}
run(command) {
command.execute();
this.done.push(command);
this.undone = []; // branch baru → buang redo future
}
undo() {
const cmd = this.done.pop();
if (cmd) { cmd.undo(); this.undone.push(cmd); }
}
redo() {
const cmd = this.undone.pop();
if (cmd) { cmd.execute(); this.done.push(cmd); }
}
}
const history = new CommandHistory();
history.run(new InsertTextCommand(editor, "Halo ", 0));
history.run(new InsertTextCommand(editor, "dunia", 5));
history.undo(); // "Halo "
history.redo(); // "Halo dunia"
Kunci Command: tiap aksi adalah object yang tahu cara execute() dan (opsional) undo().
Dunia nyata di ekosistem JS/Laravel:
- Redux actions —
{ type: "ADD_TODO", payload: {...} }adalah Command terserialisasi. Reducer adalah dispatcher. Time-travel debugging di Redux DevTools = pakai history of commands. - Laravel Jobs / queue —
dispatch(new SendWelcomeEmail($user)). Command ini diserialisasi ke Redis/SQS, dijalankan worker nanti. Clean separation: yangdispatchtidak tahu kapan dijalankan. - Node Bull queue / BullMQ — mirip Laravel queue, tiap job adalah Command.
- Event bus dengan "command" (CQRS) — Command dipisah dari Event, Command = "tolong lakukan X", Event = "X telah terjadi".
- Browser history API / DOM command —
document.execCommand("bold")(legacy API) literal Command pattern.
Command + Macro:
class MacroCommand {
constructor(commands) { this.commands = commands; }
execute() { for (const c of this.commands) c.execute(); }
undo() { for (const c of [...this.commands].reverse()) c.undo(); }
}
Rekaman macro Photoshop, Excel, atau shortcut editor tingkat lanjut — MacroCommand yang kumpulin commands selama recording.
Kapan pakai:
- Butuh undo/redo
- Butuh antrian tugas (queue, scheduler, retry)
- Butuh audit log yang bisa di-replay
- Ingin decouple pengirim request dari penerima (pengirim tidak tahu/perduli siapa yang eksekusi atau kapan)
Kapan JANGAN pakai:
- Aksi sederhana yang cukup dipanggil langsung (
user.save()) - Tidak ada kebutuhan undo, queue, atau history — pattern ini adds class overhead tanpa payoff
- Data aksi terlalu kompleks untuk diserialisasi (closure besar, reference DOM) — sulit untuk queue persisten
Trade-off:
Command bagus untuk decoupling in time (kapan eksekusi vs kapan dibuat) dan in space (siapa buat vs siapa eksekusi). Kekurangannya: menaikkan jumlah class/object dan perlu disiplin desain undo() yang benar — banyak bug memento terjadi di sini.