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:
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:
| Pengambilan | percobaan setelah diambil | Hasil handler | Keputusan |
|---|---|---|---|
| ke-1 | 1 | gagal | 1 < 3 → jadwalkan ulang |
| ke-2 | 2 | gagal | 2 < 3 → jadwalkan ulang |
| ke-3 | 3 | gagal | 3 >= 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.
Menunggu di Ujung Barisan #
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:
// 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:
node src/demo-retry.mjsOutput yang Dihasilkan:
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.