Callback fonksiyon, başka bir fonksiyona argüman olarak verilen ve o fonksiyon tarafından uygun bir anda çağrılan fonksiyondur. [1, 2].map((x) => x * 2) içindeki arrow function bir callback'tir; setTimeout(() => ..., 1000) içindeki de. İlki senkron çalışır, yani map bitmeden hemen çağrılır. İkincisi asenkron çalışır, yani mevcut kod tamamlandıktan sonra, ileri bir zamanda çağrılır. Bu ayrımı bilmek, kodunuzun hangi sırayla çalışacağını doğru tahmin etmenin temelidir.
Bu yazıda callback'in ne olduğunu, senkron ve asenkron callback farkını, iç içe callback'lerin yol açtığı "callback hell" sorununu ve bu sorunu Promise ile nasıl çözebileceğinizi adım adım gösteriyoruz. Tüm örnekler Node.js 24 ile çalıştırıldı.
Callback nedir?

JavaScript'te fonksiyonlar birinci sınıf değerlerdir: Bir değişkene atanabilir, argüman olarak verilebilir, başka bir fonksiyondan döndürülebilir. Callback bu özelliğin doğrudan sonucudur. Aşağıda hesapla fonksiyonu, ne yapacağını bilmeden işlemi kendisine verilen callback'e bırakıyor:
function hesapla(a, b, islem) {
return islem(a, b);
}
const topla = (x, y) => x + y;
console.log(hesapla(4, 6, topla)); // 10
console.log(hesapla(4, 6, (x, y) => x * y)); // 24Callback'i verirken fonksiyonun adını parantezsiz yazdığınıza dikkat edin: hesapla(4, 6, topla). topla() yazsaydınız fonksiyonu hemen çalıştırır ve sonucunu (NaN) argüman olarak vermiş olurdunuz. Callback'leri genellikle arrow function olarak yazarız; neden tercih edildiğini fonksiyon türleri yazısında anlatıyoruz.
Senkron callback: hemen çalışır
Dizi metotları (map, filter, forEach, sort, reduce) callback'lerini senkron çağırır. Metot dönmeden önce callback her öğe için çalışmış olur:
console.log("1. başla");
[10, 20].forEach((n) => console.log("2. senkron callback:", n));
console.log("3. bitir");
// 1. başla
// 2. senkron callback: 10
// 2. senkron callback: 20
// 3. bitirconst sayilar = [3, 1, 2];
console.log(sayilar.map((n) => n * 10)); // [ 30, 10, 20 ]
console.log(sayilar.filter((n) => n > 1)); // [ 3, 2 ]
console.log([...sayilar].sort((a, b) => a - b)); // [ 1, 2, 3 ]Dizi metotlarının tamamı ve callback'lerinin aldığı parametreler için map, filter ve reduce yazısına bakabilirsiniz.
Asenkron callback: sonra çalışır

Zamanlayıcılar, ağ ve dosya işlemleri gibi API'ler callback'i hemen değil, iş tamamlandığında çağırır. Gecikme 0 olsa bile setTimeout callback'i, o an çalışan kod bitmeden çalışmaz:
console.log("1. başla");
setTimeout(() => console.log("3. asenkron callback"), 0);
console.log("2. bitir");
// 1. başla
// 2. bitir
// 3. asenkron callbackBunun nedeni JavaScript'in "tamamlanana kadar çalıştır" (run-to-completion) modelidir: Bir iş başladıktan sonra bitene kadar başka bir callback araya giremez. Zamanlayıcının callback'i bir kuyrukta bekler ve sıra geldiğinde çalışır. Kuyrukların ve olay döngüsünün nasıl işlediğini event loop yazısında ayrıntılı anlatıyoruz.
| Özellik | Senkron callback | Asenkron callback |
|---|---|---|
| Ne zaman çalışır | Çağıran fonksiyon dönmeden | Mevcut kod bittikten sonra |
| Örnek API | map, filter, forEach, sort | setTimeout, Promise then, olay dinleyicileri |
| Sonucu nasıl alırsınız | Dönüş değeriyle | Callback içinde ya da Promise ile |
| try/catch ile hata yakalama | Çalışır | Dıştaki try/catch yakalamaz |
Başarı ve hata için ayrı callback'ler
Promise'ten önce asenkron API'ler genellikle iki callback alırdı: biri işlem başarılı olunca, diğeri hata olunca çağrılırdı. MDN'nin Promise rehberi de anlatıma bu tür bir API ile başlar. Aşağıda böyle bir API'yi zamanlayıcıyla taklit ediyoruz:
function kullaniciGetir(id, basarili, hatali) {
setTimeout(() => {
if (id <= 0) {
hatali(new Error("Geçersiz id"));
} else {
basarili({ id, ad: "Deniz" });
}
}, 10);
}
kullaniciGetir(
7,
(k) => console.log("Bulundu:", k.ad), // Bulundu: Deniz
(h) => console.log("Hata:", h.message),
);
kullaniciGetir(
0,
(k) => console.log("Bulundu:", k.ad),
(h) => console.log("Hata:", h.message), // Hata: Geçersiz id
);Callback hell: iç içe geçen adımlar
Birbirine bağlı birkaç asenkron adımı sırayla çalıştırmak istediğinizde, her adım bir öncekinin callback'inin içine yazılır. Adım sayısı arttıkça kod sağa doğru kayar, hata yönetimi her seviyede tekrarlanır ve akışı okumak zorlaşır. Buna "callback hell" denir:
function adim(ad, girdi, callback) {
setTimeout(() => callback(`${girdi} > ${ad}`), 10);
}
adim("sipariş", "başla", (s1) => {
adim("ödeme", s1, (s2) => {
adim("kargo", s2, (s3) => {
console.log(s3); // başla > sipariş > ödeme > kargo
});
});
});Bu örnekte hata yönetimi bile yok. Gerçek kodda her seviyeye bir hata callback'i daha eklenir. Ayrıca asenkron callback içinde fırlatılan bir hatayı, çağrıyı saran try/catch yakalayamaz, çünkü callback çalıştığında try bloğu çoktan bitmiştir:
try {
setTimeout(() => {
throw new Error("callback içinde hata");
}, 0);
} catch (e) {
console.log("Yakalandı:", e.message); // hiç çalışmaz
}
process.on("uncaughtException", (e) => console.log("try/catch kaçırdı:", e.message));
// try/catch kaçırdı: callback içinde hataSon satırdaki process.on yalnızca Node.js'e özgüdür ve burada hatanın dıştaki try/catch'ten kaçtığını göstermek için kullanıldı. MDN de setTimeout callback'inde oluşan hatanın hiçbir şey tarafından yakalanmadığını, bunun API'nin tasarımından kaynaklandığını belirtir. Hata yönetiminin genel kuralları için try/catch yazısına bakabilirsiniz.
Promise'e geçiş: zinciri düzleştirmek
Promise, asenkron bir işlemin gelecekteki sonucunu temsil eden bir nesnedir. Callback'i fonksiyona vermek yerine, fonksiyonun döndürdüğü Promise'e then() ile eklersiniz. then() her seferinde yeni bir Promise döndürdüğü için adımlar iç içe değil, alt alta yazılır ve tek bir catch() tüm zincirin hatalarını yakalar. İlk iş, callback alan eski API'yi en alt seviyede bir kez sarmalamaktır:
const adim = (ad, girdi) =>
new Promise((resolve) => setTimeout(() => resolve(`${girdi} > ${ad}`), 10));
adim("sipariş", "başla")
.then((s1) => adim("ödeme", s1))
.then((s2) => adim("kargo", s2))
.then((s3) => console.log(s3)) // başla > sipariş > ödeme > kargo
.catch((hata) => console.log("Hata:", hata.message));MDN'nin önerisi de budur: Callback alan fonksiyonları mümkün olan en alt seviyede Promise ile sarın ve sonra onları bir daha doğrudan çağırmayın. Basit bir bekleme yardımcısı şu kadar kısadır:
const bekle = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
bekle(20).then(() => console.log("20 ms geçti")); // 20 ms geçtiAynı akış async/await ile senkron koda benzer şekilde yazılabilir. Bu sözdizimi Promise'lerin üzerine kuruludur:
const adim = (ad, girdi) =>
new Promise((resolve) => setTimeout(() => resolve(`${girdi} > ${ad}`), 10));
async function surec() {
const s1 = await adim("sipariş", "başla");
const s2 = await adim("ödeme", s1);
const s3 = await adim("kargo", s2);
console.log(s3); // başla > sipariş > ödeme > kargo
}
surec();await kullanımı, hata yakalama ve paralel çalıştırma için async/await rehberine ve Promise.all, allSettled, race ve any yazısına göz atın.
Promise callback'leri her zaman asenkron çalışır
MDN, bir callback'in bazen senkron bazen asenkron çağrıldığı API tasarımından kaçınılması gerektiğini vurgular; bu durumda çağıran kod hangi sırayla çalışacağını bilemez. Promise bu belirsizliği ortadan kaldırır: then() ile eklenen callback, Promise zaten çözülmüş olsa bile hiçbir zaman senkron çağrılmaz, mevcut kod bittikten sonra çalışır:
Promise.resolve().then(() => console.log(2));
console.log(1);
// 1
// 2Sık yapılan hatalar

- Callback'i verirken fonksiyonu çağırmak:
setTimeout(calis(), 100)yerinesetTimeout(calis, 100)yazılmalı. - Asenkron callback'in sonucunu bir sonraki satırda kullanmaya çalışmak; o satır çalıştığında callback henüz çağrılmamıştır.
- Asenkron callback içindeki hatayı dıştaki
try/catchile yakalamayı beklemek. - Metodu callback olarak verip
thisdeğerinin korunacağını sanmak; ayrıntılar this yazısında. then()içinde Promise döndürmeyi unutmak ve zinciri koparmak.- Aynı callback'i bazen senkron bazen asenkron çağıran bir API yazmak.
Sık Sorulan Sorular
Her callback asenkron mudur?
Hayır. Callback yalnızca "başka bir fonksiyona verilen fonksiyon" demektir. Dizi metotlarının callback'leri senkron, zamanlayıcıların ve Promise then() callback'lerinin her zaman asenkron çalışır.
Callback hell nasıl önlenir?
En kalıcı çözüm, callback alan API'leri Promise ile sarmalayıp zincir ya da async/await kullanmaktır. Kısa vadede iç içe anonim fonksiyonlar yerine adlandırılmış fonksiyonlar kullanmak da okunurluğu artırır.
Promise callback'lerden tamamen kurtarır mı?
Hayır, Promise de callback kullanır; then() ve catch()'e verdiğiniz fonksiyonlar callback'tir. Fark, callback'in fonksiyona verilmek yerine dönen nesneye eklenmesi ve zamanlama ile hata yayılımı konusunda dilin güvence vermesidir.
setTimeout(fn, 0) neden hemen çalışmıyor?
Çünkü callback bir kuyruğa eklenir ve ancak o an çalışan kod tamamlandığında sırası gelir. Promise callback'leri ise mikro görev (microtask) kuyruğunda bekler ve zamanlayıcı callback'lerinden önce çalışır; ayrıntılar event loop 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: JavaScript öğrenme rehberi · Promise nedir · async/await · Event loop · Dizi metotları · Node.js nedir