Node.js'te 'uncaughtException', hiçbir yerde yakalanmayan bir istisna olay döngüsüne kadar ulaştığında process nesnesinde tetiklenen olaydır; 'unhandledRejection' ise reddedilen bir Promise'e olay döngüsünün bir turu içinde hata işleyici bağlanmadığında tetiklenir. Varsayılan olarak ikisi de aynı sonuca götürür: Node.js yığın izini stderr'e yazar ve süreci 1 koduyla sonlandırır. v15.0.0'dan beri işlenmemiş Promise retleri de varsayılan olarak süreci çökertir.

Bu olaylara dinleyici ekleyerek varsayılan davranışı değiştirebilirsiniz, ama belgeler bunun son çare olduğunu açıkça söylüyor: yakalanmamış bir istisnadan sonra uygulamanın durumu belirsizdir ve doğru kullanım, kaynakları senkron olarak temizleyip süreci kapatmaktır. Bu yazıda iki olayı, --unhandled-rejections bayrağının beş modunu, 'uncaughtExceptionMonitor' ve 'rejectionHandled' olaylarını ve EventEmitter'daki 'error' olayının buna nasıl bağlandığını Node.js v26.13.0 ile çalıştırılmış örneklerle gösteriyoruz. Çıktılardaki dosya yollarını kısalttık.

Varsayılan davranış: yazdır ve çık

Varsayılan davranış: yazdır ve çık

Aşağıdaki betik bozuk bir JSON metnini ayrıştırmaya çalışıyor ve hatayı yakalamıyor. Hata olay döngüsüne kadar çıkınca Node.js süreci sonlandırıyor; bu yüzden 100 milisaniye sonra çalışacak zamanlayıcı hiç çalışmıyor.

javascript
// cokus.mjs
setTimeout(() => {
  console.log('bu satır hiç yazılmaz');
}, 100);

JSON.parse('{bozuk');
bash
$ node cokus.mjs
<anonymous_script>:1
{bozuk
 ^

SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)
    at JSON.parse (<anonymous>)
    at file:///home/kullanici/proje/cokus.mjs:5:6
    ...

Node.js v26.13.0
$ echo $?
1

Belgedeki çıkış kodları listesinde 1, "Uncaught Fatal Exception" yani bir domain ya da 'uncaughtException' dinleyicisi tarafından ele alınmamış istisna anlamına gelir. Belgeye göre bu durumda daha önce ayarlanmış bir process.exitCode değeri de geçersiz kalır. Hatanın kendisini önlemek ise dil düzeyinde try/catch işidir; bunun için JavaScript hata yönetimi yazısına bakın.

uncaughtException dinleyicisi ve origin argümanı

process.on('uncaughtException', (err, origin) => {}) ile bir dinleyici eklediğinizde varsayılan davranış devre dışı kalır: Node.js yığın izini yazmaz ve süreci kendisi kapatmaz. Dinleyici içinde process.exitCode ayarlarsanız süreç bittiğinde o kodla çıkar; ayarlamazsanız belgeye göre çıkış kodu 0 olur. İkinci argüman origin, hatanın senkron bir istisnadan mı ('uncaughtException') yoksa işlenmemiş bir Promise retinden mi ('unhandledRejection') geldiğini söyler.

javascript
// yakala.mjs
import fs from 'node:fs';

process.on('uncaughtException', (err, origin) => {
  fs.writeSync(process.stderr.fd, `yakalandı: ${err.message} (kaynak: ${origin})\n`);
  process.exitCode = 70;
});

setTimeout(() => console.log('zamanlayıcı yine de çalıştı'), 50);
olmayanFonksiyon();

// $ node yakala.mjs; echo $?
// çıktı (stderr): yakalandı: olmayanFonksiyon is not defined (kaynak: unhandledRejection)
// çıktı: zamanlayıcı yine de çalıştı
// çıktı: 70

Bu çıktıda iki önemli nokta var. Birincisi, dinleyici eklendiği için süreç ölmedi ve zamanlayıcı çalıştı; yani uygulama hatadan sonra çalışmayı sürdürdü. İkincisi, senkron bir ReferenceError için origin değeri 'unhandledRejection' geldi. Bunun nedeni dosyanın bir ES modülü olmasıdır: belgeye göre hata, giriş noktasının ES modülü yükleme aşamasında oluşursa da bu değer kullanılır. Aynı kodu CommonJS ile ya da bir zamanlayıcının içinde çalıştırdığınızda 'uncaughtException' görürsünüz.

javascript
// kaynak.mjs
process.on('uncaughtException', (err, origin) => {
  console.log(`${err.message} => ${origin}`);
});

setTimeout(() => {
  throw new Error('zamanlayıcı içinde');
}, 10);

Promise.reject(new Error('reddedilen promise'));
// çıktı: reddedilen promise => unhandledRejection
// çıktı: zamanlayıcı içinde => uncaughtException

Örnekte 'unhandledRejection' dinleyicisi olmadığı için reddedilen Promise varsayılan throw modunda bir yakalanmamış istisnaya dönüştü ve aynı dinleyiciye düştü. Belgedeki örneklerde dinleyici içinde console.log yerine fs.writeSync(process.stderr.fd, ...) kullanılır; süreç hemen kapanacaksa senkron yazmak mesajın kaybolmamasını garanti etmeye yardımcı olur.

Neden uygulamayı çalıştırmaya devam etmemelisiniz?

Belgeler bu konuda alışılmadık derecede açık konuşuyor: 'uncaughtException' kaba bir mekanizmadır ve "hata olursa devam et" anlamında kullanılmamalıdır. Yakalanmamış bir istisna, uygulamanın tanımsız bir durumda olduğu anlamına gelir. Yarım kalmış bir yazma işlemi, açık kalmış bir veritabanı işlemi ya da tutarsız bir önbellek, sonraki isteklerde öngörülemeyen hatalara yol açabilir. Belgenin önerdiği doğru kullanım, ayrılmış kaynakları (dosya tanımlayıcıları, tanıtıcılar) senkron olarak temizledikten sonra süreci kapatmaktır.

Dinleyicinin kendisi hata fırlatırsa ne olur? Belgeye göre bu hata yakalanmaz, sonsuz döngüyü önlemek için süreç sıfırdan farklı bir kodla çıkar. Denememizde çıkış kodu 7 oldu; çıkış kodları listesinde 7, iç istisna işleyicisinin çalışırken hata vermesi anlamına gelir ve örnek olarak bir 'uncaughtException' dinleyicisinin hata fırlatması verilir.

javascript
// ici.mjs
process.on('uncaughtException', () => {
  throw new Error('dinleyicinin içinden hata');
});
throw new Error('ilk hata');

// $ node ici.mjs; echo $?   (çıktı kısaltıldı)
// çıktı (stderr): Error: dinleyicinin içinden hata
// çıktı (stderr):     at process.<anonymous> (file:///home/kullanici/proje/ici.mjs:2:9)
// çıktı: 7

Çökmüş bir uygulamayı güvenilir biçimde yeniden başlatmak için belge, ayrı bir süreçte çalışan harici bir izleyici kullanmayı öneriyor. Bu iş için systemd, Docker'ın yeniden başlatma politikası ya da bir süreç yöneticisi kullanabilirsiniz; systemd servis dosyası rehberi ve Node.js cluster ve PM2 yazıları bu kurulumları anlatıyor.

Kontrollü kapanış deseni

Pratikte 'uncaughtException' dinleyicisi şu üç işi yapmalıdır: hatayı senkron olarak loglamak, yeni iş kabul etmeyi durdurmak ve süreci kısa bir süre içinde kapatmak. Aşağıdaki örnek, sunucuyu kapatıyor, çıkış kodunu 1 yapıyor ve kapanma bir saniyeden uzun sürerse süreci zorla sonlandıran bir zamanlayıcı kuruyor. Zamanlayıcıya unref() çağrıldığı için süreç erken biterse bu zamanlayıcı onu açık tutmaz.

javascript
// kapanis.mjs
import http from 'node:http';
import fs from 'node:fs';

const sunucu = http.createServer((req, res) => res.end('tamam'));

process.on('uncaughtException', (err, origin) => {
  fs.writeSync(process.stderr.fd, `ölümcül hata (${origin}): ${err.message}\n`);
  sunucu.close();
  process.exitCode = 1;
  setTimeout(() => process.exit(1), 1000).unref();
});

sunucu.listen(0, '127.0.0.1', () => {
  console.log('sunucu dinliyor');
  setTimeout(() => {
    throw new Error('beklenmeyen durum');
  }, 20);
});
// çıktı: sunucu dinliyor
// çıktı (stderr): ölümcül hata (uncaughtException): beklenmeyen durum
// (süreç 1 koduyla çıkar)

Gerçek bir uygulamada bu dinleyiciye veritabanı bağlantılarını kapatmak ya da kuyruğu boşaltmak gibi adımlar da eklenir. SIGTERM ile gelen normal kapanış ve sıfır kesintili yeniden başlatma ayrı bir konudur ve graceful shutdown başlığı altında anlatılıyor. Zamanlayıcıların ref ve unref davranışı için setTimeout ve setInterval yazısına, çıkış kodları için process.exit ve çıkış kodları yazısına bakın.

uncaughtExceptionMonitor: davranışı değiştirmeden izlemek

Hataları yalnızca bir izleme aracına göndermek, ama Node.js'in varsayılan çökme davranışını korumak istiyorsanız 'uncaughtExceptionMonitor' olayını kullanın. Bu olay 'uncaughtException' olayından önce tetiklenir; dinleyici eklemek hiçbir şeyi değiştirmez ve başka bir dinleyici yoksa süreç yine çöker.

javascript
// monitor.mjs
process.on('uncaughtExceptionMonitor', (err, origin) => {
  console.log('izleyici:', err.message, origin);
});
throw new Error('kritik hata');

// $ node monitor.mjs; echo $?   (çıktı kısaltıldı)
// çıktı: izleyici: kritik hata unhandledRejection
// çıktı (stderr): Error: kritik hata
// çıktı (stderr):     at file:///home/kullanici/proje/monitor.mjs:4:7
// çıktı: 1

Burada da hata ES modülünün üst düzeyinde oluştuğu için origin değeri 'unhandledRejection' çıktı. İzleme kodu için bu olay daha güvenli bir tercihtir, çünkü yanlışlıkla süreci tanımsız durumda çalışır bırakma riskini ortadan kaldırır.

unhandledRejection: işlenmemiş Promise retleri

Bir async fonksiyon içinde fırlatılan hata, fonksiyonun döndürdüğü Promise'i reddeder. O Promise'i await etmez ya da .catch() bağlamazsanız ret işlenmemiş kalır. Varsayılan throw modunda Node.js önce 'unhandledRejection' olayını yayar; dinleyici yoksa reddi yakalanmamış istisna olarak fırlatır.

javascript
// reddet.mjs
async function kullaniciGetir() {
  throw new Error('veritabanı yanıt vermedi');
}

kullaniciGetir(); // await veya .catch() yok
setTimeout(() => console.log('bu satır yazılmaz'), 100);

// $ node reddet.mjs; echo $?   (çıktı kısaltıldı)
// çıktı (stderr): Error: veritabanı yanıt vermedi
// çıktı (stderr):     at kullaniciGetir (file:///home/kullanici/proje/reddet.mjs:2:9)
// çıktı: 1

Ret nedeni bir Error değilse, örneğin düz bir metinse, Node.js onu kod alanı ERR_UNHANDLED_REJECTION olan bir UnhandledPromiseRejection hatasına sarar. Mesaj, hatanın bir async fonksiyonda catch bloğu olmadan fırlatılmasından ya da .catch() ile işlenmeyen bir Promise'ten kaynaklandığını açıklar ve ret nedenini tırnak içinde verir. Yığın izi sizin kodunuzu değil Node.js'in iç fonksiyonlarını gösterir; bu, Promise'leri her zaman Error nesneleriyle reddetmek için güçlü bir nedendir.

javascript
// metin.mjs
Promise.reject('sadece bir metin');

// çıktı (stderr): UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "sadece bir metin".
// çıktı (stderr):     at throwUnhandledRejectionsMode (node:internal/process/promises:322:7)
// ... (kısaltıldı)
// çıktı (stderr):   code: 'ERR_UNHANDLED_REJECTION'

rejectionHandled: geç bağlanan işleyiciler

Bir Promise'e hata işleyici bir olay döngüsü turundan daha geç bağlanırsa, önce 'unhandledRejection', işleyici bağlandığında da 'rejectionHandled' tetiklenir. Belge bunu şöyle özetliyor: senkron kodda işlenmemiş istisnalar listesi yalnızca büyür, Promise'lerde ise liste büyüyüp küçülebilir. İki olayı birlikte dinleyerek bu listeyi takip edebilirsiniz.

javascript
// reddet2.mjs
process.on('unhandledRejection', (reason, promise) => {
  console.log('işlenmemiş ret:', reason.message);
});
process.on('rejectionHandled', (promise) => {
  console.log('ret sonradan işlendi');
});

const p = Promise.reject(new Error('geç yakalanacak'));
setTimeout(() => {
  p.catch(() => console.log('catch çalıştı'));
}, 50);
// çıktı: işlenmemiş ret: geç yakalanacak
// çıktı: catch çalıştı
// çıktı: ret sonradan işlendi

Promise kavramı ve async/await ile doğru hata yakalama dil düzeyinde Promise nedir ve async/await yazılarında anlatılıyor. Burada odaklandığımız şey Node.js'in bu retlere çalışma zamanında nasıl tepki verdiği.

--unhandled-rejections modları

--unhandled-rejections modları: throw (varsayılan), strict, warn, warn-with-error-code

İşlenmemiş retlere verilecek tepkiyi --unhandled-rejections=mod bayrağıyla seçersiniz. Varsayılan mod v15.0.0'dan beri throw; daha önce yalnızca uyarı veriliyordu. Aşağıdaki tablo, belgedeki tanımları ve aynı betiği 'unhandledRejection' dinleyicisi olan ve olmayan iki hâliyle çalıştırdığımızda aldığımız çıkış kodlarını gösteriyor. Betik bir Promise'i reddediyor ve 50 milisaniye sonra "süreç devam etti" yazıyor.

ModBelgedeki tanımDinleyici varkenDinleyici yokken
throw (varsayılan)Önce unhandledRejection yayılır; dinleyici yoksa ret yakalanmamış istisna olurDinleyici çalıştı, süreç devam etti, çıkış 0Çöktü, çıkış 1
strictRet doğrudan yakalanmamış istisna olur; istisna ele alınırsa unhandledRejection yayılırÇöktü, çıkış 1Çöktü, çıkış 1
warnDinleyici olsa da olmasa da uyarı yazılırDinleyici ve uyarı, süreç devam etti, çıkış 0Uyarı, süreç devam etti, çıkış 0
warn-with-error-codeunhandledRejection yayılır; dinleyici yoksa uyarı yazılır ve çıkış kodu 1 yapılırDinleyici çalıştı, uyarı yok, çıkış 0Uyarı, süreç devam etti, çıkış 1
noneTüm uyarılar susturulurDinleyici çalıştı, çıkış 0Sessiz, süreç devam etti, çıkış 0

strict modunda dinleyicinin hiç çağrılmadığına dikkat edin: belgeye göre bu modda ret önce yakalanmamış istisna olarak fırlatılır ve 'unhandledRejection' yalnızca o istisna bir 'uncaughtException' dinleyicisi tarafından ele alınırsa yayılır. warn modunda stderr'e UnhandledPromiseRejectionWarning türünde iki uyarı yazılır: biri hatanın kendisi, diğeri retlerin işlenmesi gerektiğini anlatan genel açıklama. none ise hataları tamamen görünmez yapar ve üretim için tehlikelidir. Bir istisna da var: belgeye göre ret, giriş noktasının ES modülü statik yükleme aşamasında olursa hangi mod seçilirse seçilsin yakalanmamış istisna olarak fırlatılır.

bash
$ node --unhandled-rejections=warn-with-error-code modlar2.mjs
(node:PID) UnhandledPromiseRejectionWarning: Error: ret
    at file:///home/kullanici/proje/modlar2.mjs:1:16
    ...
süreç devam etti
$ echo $?
1

warn-with-error-code, uzun süre çalışan bir sürecin işlenmemiş retlerde ölmesini istemediğiniz ama CI veya betik çalıştırıcısının hatayı fark etmesini istediğiniz durumlar için uygundur. Bayrağı her seferinde yazmak yerine NODE_OPTIONS ya da node.config.json ile verebilirsiniz; NODE_OPTIONS ve CLI bayrakları yazısında ayar dosyasıyla unhandled-rejections verdiğimiz bir örnek var.

EventEmitter ve dinleyicisiz error olayı

Akışlar, soketler ve sunucular gibi EventEmitter tabanlı nesneler hataları 'error' olayıyla bildirir. Belgeye göre bu olay için dinleyici yoksa hata fırlatılır ve bir 'uncaughtException' dinleyicisi yoksa süreç çöker. Hata başka bir anda, örneğin bir setImmediate içinde yayıldığı için çağıran koddaki try/catch onu yakalayamaz.

javascript
// emitter.mjs
import { EventEmitter } from 'node:events';

const kuyruk = new EventEmitter();
setImmediate(() => {
  kuyruk.emit('error', new Error('kuyruk bağlantısı koptu'));
});

// $ node emitter.mjs; echo $?   (çıktı kısaltıldı)
// çıktı (stderr): node:events:505
// çıktı (stderr):     throw er; // Unhandled 'error' event
// çıktı (stderr):     ^
// çıktı (stderr): Error: kuyruk bağlantısı koptu
// çıktı (stderr):     at Immediate.<anonymous> (file:///home/kullanici/proje/emitter.mjs:5:24)
// çıktı (stderr): Emitted 'error' event at:
// çıktı (stderr):     at Immediate.<anonymous> (file:///home/kullanici/proje/emitter.mjs:5:10)
// çıktı: 1

Çözüm her EventEmitter'a, özellikle ağ ve dosya akışlarına, bir 'error' dinleyicisi eklemektir. Akışları stream.pipeline() ile birleştirmek hata yönetimini tek bir yerde toplar. Ayrıntılar EventEmitter kullanımı ve stream.pipeline ve backpressure yazılarında.

setUncaughtExceptionCaptureCallback

process.setUncaughtExceptionCaptureCallback(fn), yakalanmamış istisnalar için tek bir yakalama fonksiyonu kurar. Bu fonksiyon ayarlıysa 'uncaughtException' olayı hiç yayılmaz; --abort-on-uncaught-exception verilmiş olsa bile süreç abort etmez. Kaldırmak için null verilir; başka bir fonksiyon zaten ayarlıyken yeni bir fonksiyon vermek hata fırlatır. Birden fazla geri çağrının bir arada çalışması gerekiyorsa v25.9.0 ile gelen process.addUncaughtExceptionCaptureCallback() kullanılabilir, ancak bu fonksiyon deneysel (Stability 1).

javascript
// capture.mjs
process.setUncaughtExceptionCaptureCallback((err) => {
  console.log('capture:', err.message);
});
process.on('uncaughtException', () => console.log('bu dinleyici çağrılmaz'));
throw new Error('yakalanacak');
// çıktı: capture: yakalanacak

Bu API daha çok kütüphane ve araç yazarları içindir. Uygulama kodunda 'uncaughtException' ile kontrollü kapanış ya da 'uncaughtExceptionMonitor' ile izleme çoğu zaman yeterlidir.

Node.js'teki hata sınıfları

Node.js'teki hata sınıfları: Error, TypeError, RangeError, ReferenceError

Bu olaylara düşen hataların sınıfı, sorunun kaynağı hakkında ipucu verir. Belgeler Node.js'in ürettiği tüm hataların Error sınıfından türediğini belirtir ve sınıfları şöyle tanımlar.

SınıfNe zaman görülür
ErrorGenel hata; modül bulunamaması gibi pek çok Node.js hatası
TypeErrorBir argüman izin verilmeyen tipte, örneğin ERR_INVALID_ARG_TYPE
RangeErrorBir değer kabul edilen aralığın dışında, örneğin ERR_OUT_OF_RANGE
ReferenceErrorTanımlanmamış bir değişkene erişim; genellikle yazım hatası
SyntaxErrorGeçersiz JavaScript ya da JSON
SystemErrorİşletim sistemi kısıtı: ENOENT, EADDRINUSE gibi kodlar
AssertionErrornode:assert doğrulaması başarısız oldu
DOMExceptionfetch, AbortController ve Web Streams gibi web platformu API'leri

Belgeye göre bazı istisnalar JavaScript katmanında kurtarılamaz ve süreci her zaman çökertir; C++ katmanındaki assert() kontrolleri ve abort() çağrıları buna örnektir. Bu tür çöküşler 'uncaughtException' dinleyicisine ulaşmaz. Hata kodlarının ayrıntısı için Node.js ERR_* hata kodları ve sistem hataları yazılarına bakın.

Sık yapılan hatalar

uncaughtException ile hatayı yutup devam etmek. Bu, belgelerin açıkça uyardığı kullanımdır. Dinleyiciyi yalnızca loglama ve kontrollü kapanış için kullanın.

Dinleyicide exitCode ayarlamamak. Dinleyici varken süreç kendiliğinden kapanırsa çıkış kodu 0 olur ve süreç yöneticiniz bir hata olduğunu anlamaz. process.exitCode = 1 ayarlayın.

Asenkron loglamaya güvenmek. Süreç hemen kapanacaksa ağ üzerinden log göndermek tamamlanmayabilir. Kritik mesajı önce fs.writeSync ile stderr'e yazın.

Async fonksiyonu await etmeden çağırmak. Dönen Promise'i bırakmak, reddedildiğinde süreci çökertir. Ya await edin ya da .catch() bağlayın.

Promise'i Error olmayan bir değerle reddetmek. Düz metinle yapılan retlerde yığın izi sizin kodunuzu göstermez. Her zaman new Error(...) kullanın.

--unhandled-rejections=none ile sorunu gizlemek. Bu mod uyarıları da kapatır ve hataları tamamen görünmez yapar. Geçiş döneminde gerekiyorsa warn-with-error-code tercih edin.

Sık Sorulan Sorular

İşlenmemiş Promise retleri neden programımı kapatıyor?

v15.0.0'dan beri --unhandled-rejections bayrağının varsayılan değeri throw. Bu modda dinleyicisi olmayan bir ret, yakalanmamış istisna gibi ele alınır ve süreç 1 koduyla çıkar. Belgelerdeki DEP0018 maddesi de bu nedenle End-of-Life durumundadır.

uncaughtException mı, uncaughtExceptionMonitor mı kullanmalıyım?

Yalnızca hataları kaydetmek istiyorsanız 'uncaughtExceptionMonitor' daha güvenlidir, çünkü varsayılan çökme davranışını değiştirmez. Kapanmadan önce kaynakları temizlemeniz gerekiyorsa 'uncaughtException' kullanın ve sonunda süreci kapatın.

ES modülünde neden origin değeri unhandledRejection geliyor?

ES modülleri Promise tabanlı bir süreçle yüklenip çalıştırılır. Belgeye göre giriş noktasının ES modülü yükleme aşamasında oluşan hatalar da 'unhandledRejection' kaynağıyla raporlanır. Zamanlayıcı veya olay dinleyicisi içinde fırlatılan hatalarda 'uncaughtException' görürsünüz.

Worker thread içindeki yakalanmamış hata ana süreci çökertir mi?

Belgeye göre bir worker thread yakalanmamış bir istisna fırlatırsa ana thread'deki Worker nesnesinde 'error' olayı yayılır ve worker sonlandırılır. Bu olayı dinleyerek hatayı ana thread'de ele alabilirsiniz; worker kullanımı worker_threads kullanımı yazısında.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Node.js öğrenme rehberi · Sistem hataları · Node.js hata kodları · EventEmitter kullanımı · Node.js cluster ve PM2