Pekerjaan latar belakang hidup di dunia yang tidak stabil: pelayan email sedang sibuk, koneksi database terputus sejenak, data tidak lengkap. Kegagalan semacam itu bersifat sesaat — mencoba lagi lima detik kemudian sering kali berhasil. Pada bagian ini kita menyelesaikan jalur gagal yang pada Bagian 2 kita titipkan ke satu function: menghitung percobaan, memutuskan kapan berhenti, dan menangkap pesan galat sebagai data pekerjaan. Tujuan akhirnya bukan menghilangkan kegagalan, melainkan memastikan setiap kegagalan tercatat, terbatas, dan terlihat.

Percobaan dan Batasnya #

Seluruh kebijakan percobaan ulang berada di tanganiKegagalan — function yang dipanggil prosesSatu setiap kali handler melempar error:

js src/pekerja.js
  async function tanganiKegagalan(pekerjaan, err) {
    const terkini = antrean.cari(pekerjaan.id);
    if (terkini.percobaan >= maksimalPercobaan) {
      bus.emit('PekerjaanGagal', antrean.tandaiGagal(pekerjaan.id, err.message));
    } else {
      antrean.jadwalkanUlang(pekerjaan.id);
    }
  }

Kuncinya ada pada baris 21-22: keputusan diambil dengan membandingkan percobaan pekerjaan yang baru saja diambil dengan maksimalPercobaan. Nilai percobaan bertambah setiap kali ambilBerikutnya mengambil pekerjaan — dihitung sejak pengambilan, bukan sejak kegagalan — sehingga satu pekerjaan yang selalu gagal menempuh perjalanan yang bisa diprediksi persis:

Pengambilanpercobaan setelah diambilHasil handlerKeputusan
ke-11gagal1 < 3 → jadwalkan ulang
ke-22gagal2 < 3 → jadwalkan ulang
ke-33gagal3 >= 3 → tandai gagal

Tiga baris tabel itu adalah seluruh kontrak kegagalan: pekerjaan diuji maksimal tiga kali (nilai bawaan maksimalPercobaan), dan setiap pengujian tercatat di field percobaan. Batas ini melindungi antrean dari pekerjaan yang tidak akan pernah berhasil — tanpa batas, satu pekerjaan bermasalah akan berputar selamanya dan menghabiskan waktu worker.

Cabang else pada baris 25 memanggil jadwalkanUlang, yang kita kenal pada Bagian 1: status kembali ke menunggu dan id didorong ke ujung barisan. Penempatan di ujung bukan detail kecil. Ketika pekerjaan #3 gagal sementara #4 dan #5 masih menunggu, urutan pengambilan berikutnya adalah #4, #5, baru #3 — pekerjaan lain tidak boleh menunggu gara-gara satu pekerjaan yang sedang diulang. Test pada Bagian 4 mengunci perilaku ini lewat urutan pengambilan yang pasti: [1, 2, 1] untuk dua pekerjaan yang pertama gagal lalu berhasil.

Pesan Galat sebagai Data #

Perhatikan argumen tandaiGagal pada baris 23: err.message, bukan err keseluruhan. Sebuah Error membawa stack trace yang berisi path berkas di komputer tempat kode berjalan — informasi yang tidak berguna bagi pembaca status pekerjaan dan berpotensi membocorkan struktur direktori internal. Field pesan pada pekerjaan cukup memuat satu kalimat yang bisa ditampilkan: Penerima email tidak valid: bukan-email.

Pesan yang tersimpan selalu berasal dari percobaan terakhir, sehingga laporan kegagalan menjelaskan keadaan pada saat pekerjaan benar-benar diberhentikan. Untuk pekerjaan yang gagal sebentar lalu berhasil, pesan tidak pernah muncul — field pesan hanya terisi pada status gagal, dan pekerjaan yang berhasil justru membawa field hasil.

Demo Gagal Sekali lalu Berhasil #

Sesi demo berikut menguji kedua ujung kebijakan. Bagian pertama menyuntikkan handler khusus yang gagal pada percobaan pertama lalu berhasil — pola yang lazim untuk menguji percobaan ulang tanpa kebetulan:

js src/demo-retry.mjs
// Handler khusus: gagal pada percobaan pertama, berhasil pada percobaan kedua.
let panggilan = 0;
const handlerGoyah = {
  'sinkronisasi-data': async () => {
    panggilan += 1;
    if (panggilan < 2) throw new Error('sumber data belum siap');
    return 'sinkronisasi selesai';
  },
};

const pemrosesGoyah = buatPemroses({ handler: handlerGoyah });

Pemroses kedua memakai handler bawaan dengan penerima yang tidak valid — pekerjaan yang dipastikan gagal selamanya:

bash
node src/demo-retry.mjs

Output yang Dihasilkan:

text
gagal lalu berhasil: {"id":1,"jenis":"sinkronisasi-data","data":{},"status":"selesai","percobaan":2,"hasil":"sinkronisasi selesai"}
gagal tiga kali    : {"id":1,"jenis":"kirim-email","data":{"penerima":"bukan-email","subjek":"Uji kegagalan"},"status":"gagal","percobaan":3,"pesan":"Penerima email tidak valid: bukan-email"}

Dua baris output itu meringkas seluruh bagian ini. Baris pertama: pekerjaan sinkronisasi-data berakhir selesai dengan percobaan: 2 — gagal sekali, dicoba ulang sekali, berhasil. Baris kedua: pekerjaan kirim-email dengan penerima bukan-email berakhir gagal dengan percobaan: 3 dan pesan galat dari handler tersimpan rapi. Kegagalan kini berhenti menjadi kejadian yang mengganggu; ia menjadi data berstatus gagal yang bisa disaring dan dihitung. Bagian 4 membangun tepat itu: pemantauan yang membuat keadaan antrean terlihat dari luar, plus test yang mengunci seluruh perilaku.