JavaScript'te Promise, henüz bitmemiş bir asenkron işlemin ileride üreteceği sonucu (ya da hatayı) temsil eden bir nesnedir. Bir fonksiyona başarı ve hata callback'leri vermek yerine, fonksiyon size bir Promise döndürür; siz de bu nesneye then(), catch() ve finally() ile ne yapılacağını bağlarsınız. Promise üç durumdan birindedir: pending (beklemede), fulfilled (başarıyla tamamlandı) ve rejected (reddedildi).

Bu yazıda Promise'in durumlarını, nasıl oluşturulduğunu, then/catch/finally zincirinin kurallarını ve en sık yapılan hataları Node.js 24 üzerinde çalıştırılmış örneklerle anlatıyoruz. Promise'leri async/await ile kullanmak için async/await rehberine, birden fazla Promise'i birlikte yönetmek için Promise.all, allSettled, race ve any karşılaştırmasına bakabilirsiniz.

Promise neden var: callback'ten Promise'e

Promise'ten önce asenkron işlemler sonucu bir callback fonksiyonuna iletirdi. Arka arkaya birkaç işlem gerektiğinde her adım bir öncekinin callback'i içine yazılır ve iç içe geçmiş, okunması zor bir "piramit" ortaya çıkardı. Ayrıca her seviyede hata callback'ini ayrı ayrı vermek gerekirdi. Bu yapının ayrıntıları için callback fonksiyon yazımıza göz atabilirsiniz.

Promise bu sorunu tersine çevirir: callback'i fonksiyona geçirmek yerine, fonksiyonun döndürdüğü nesneye eklersiniz. then() her çağrıda yeni bir Promise döndürdüğü için adımları düz bir zincir halinde yazabilir, hatayı da zincirin sonunda tek bir catch() ile yakalayabilirsiniz.

Promise'in durumları: pending, fulfilled, rejected

Promise'in durumları: pending, fulfilled, rejected: pending, fulfilled, rejected, settled

Her Promise pending durumunda başlar. İşlem başarılı olursa bir değerle fulfilled, başarısız olursa bir sebeple (genellikle bir Error nesnesiyle) rejected durumuna geçer. Fulfilled ya da rejected olan Promise'e settled (sonuçlanmış) denir. Sonuçlanan bir Promise bir daha durum değiştirmez.

javascript
const bekle = (ms, deger) => new Promise((resolve) => setTimeout(() => resolve(deger), ms));

const p = bekle(100, 'veri');
console.log(p); // Promise { <pending> }

p.then((d) => {
  console.log(d); // veri
  console.log(p); // Promise { 'veri' }
});

const r = Promise.reject('olmadı');
r.catch(() => {});
console.log(r); // Promise { <rejected> 'olmadı' }

Node.js, konsolda fulfilled bir Promise'i doğrudan değeriyle (Promise { 'veri' }), reddedilmiş olanı <rejected> etiketiyle gösterir. Tarayıcı konsolları aynı bilgiyi farklı biçimde yazabilir.

DurumAnlamıDeğişebilir mi?
pendingİşlem sürüyor, sonuç yokEvet, fulfilled veya rejected olur
fulfilledİşlem başarılı, bir değer varHayır
rejectedİşlem başarısız, bir sebep (hata) varHayır
settledfulfilled veya rejected (ortak ad)Hayır

Promise oluşturmak: new Promise(executor)

Kendi Promise'inizi new Promise(executor) ile oluşturursunuz. executor fonksiyonu iki parametre alır: resolve ve reject. Executor hemen ve senkron olarak çalışır; yalnızca then ile bağladığınız işleyiciler sonradan çalışır. Executor içinde fırlatılan hata Promise'i otomatik olarak reddeder.

javascript
const p = new Promise((resolve) => {
  console.log('executor hemen çalışır');
  resolve();
});
console.log('yapıcıdan sonra');
// executor hemen çalışır
// yapıcıdan sonra

Bir Promise yalnızca bir kez sonuçlanır. İlk resolve veya reject çağrısı geçerlidir, sonrakiler sessizce yok sayılır:

javascript
const s = new Promise((resolve, reject) => {
  resolve('ilk');
  resolve('ikinci');       // etkisiz
  reject(new Error('x'));  // etkisiz
});
s.then((v) => console.log(v)); // ilk

MDN, Promise() yapıcısının esas olarak henüz Promise desteklemeyen eski callback API'lerini sarmak için kullanılmasını önerir. Klasik örnek setTimeout'u bir bekle(ms) fonksiyonuna çevirmektir. Elinizdeki fonksiyon zaten Promise döndürüyorsa onu ayrıca new Promise içine sarmanıza gerek yoktur. Promise yalnızca new ile çağrılabilir; new olmadan çağırmak TypeError fırlatır.

then, catch ve finally

then, catch ve finally: Normal bir değer döndürdü, Bir Promise (thenable) döndürdü, Hata fırlattı (throw), İlgili işleyici hiç yok

then(onFulfilled, onRejected) en fazla iki işleyici alır: ilki başarı, ikincisi ret durumu için. catch(onRejected) aslında then(null, onRejected) kısaltmasıdır. finally(onFinally) ise sonuç ne olursa olsun çalışan bir temizlik işleyicisi ekler. Üçü de yeni bir Promise döndürür, bu yüzden zincirlenebilir.

Yeni Promise'in durumu, çalışan işleyicinin ne yaptığına bağlıdır. MDN'deki kurallar şöyle özetlenebilir:

İşleyici ne yaptı?then'in döndürdüğü yeni Promise
Normal bir değer döndürdüO değerle fulfilled olur
Bir Promise (thenable) döndürdüO Promise ile aynı sonuca ulaşır
Hata fırlattı (throw)Fırlatılan hatayla rejected olur
İlgili işleyici hiç yokÖnceki Promise'in durumunu aynen taşır

finally bu tabloya uymaz: işleyicisi argüman almaz ve döndürdüğü değer yok sayılır. Yani finally çoğu zaman "şeffaftır" ve önceki sonucu olduğu gibi aktarır. İstisna, finally içinde hata fırlatmak veya reddedilen bir Promise döndürmektir; bu durumda zincir o hatayla reddedilir.

javascript
Promise.resolve(2).finally(() => 77).then((v) => console.log('finally sonrası:', v));
// finally sonrası: 2

Promise.resolve(2).then(() => 77, () => 77).then((v) => console.log('then sonrası:', v));
// then sonrası: 77

Promise.reject(3).finally(() => 88).catch((e) => console.log('hâlâ reddedilmiş:', e));
// hâlâ reddedilmiş: 3

Zincirleme (chaining) nasıl çalışır?

Zincirde her then bir önceki adımın döndürdüğü değeri alır. Bir adım Promise döndürürse sonraki adım o Promise'in sonucunu bekler; Promise'in kendisini değil, çözülmüş değerini alır. Bir adımda hata fırlatılırsa araya giren then işleyicileri atlanır ve kontrol ilk uygun catch'e geçer. catch bir değer döndürürse zincir oradan başarılı olarak devam eder.

javascript
Promise.resolve(2)
  .then((x) => x * 10)
  .then((x) => Promise.resolve(x + 1))
  .then((x) => {
    console.log(x); // 21
    throw new Error('zincir koptu');
  })
  .then(() => console.log('çalışmaz'))
  .catch((e) => {
    console.log('yakalandı:', e.message); // yakalandı: zincir koptu
    return 'yedek';
  })
  .then((v) => console.log('devam:', v)) // devam: yedek
  .finally(() => console.log('temizlik')); // temizlik

Bu davranış senkron koddaki try/catch bloğuna çok benzer. Senkron tarafta hataların nasıl ele alındığını try/catch hata yönetimi yazımızda anlattık.

İç içe zincir ne zaman mantıklı?

Basit zincirleri düz tutmak en iyisidir. MDN, iç içe zinciri yalnızca bir catch'in kapsamını daraltmak istediğinizde öneriyor: örneğin isteğe bağlı bir adımın hatasını yutup ana akışa devam etmek, ama kritik adımların hatasını dıştaki catch'e bırakmak. Bunun dışında iç içe yazım genellikle dikkatsiz bir kompozisyonun sonucudur.

Zamanlama: then işleyicisi asla senkron çalışmaz

Promise zaten sonuçlanmış olsa bile then ile eklenen işleyici hemen çalışmaz. İşleyici microtask kuyruğuna konur ve mevcut senkron kod tamamen bittikten sonra çalışır. Bu, her zaman aynı sırayla çalışan, öngörülebilir bir API sağlar.

javascript
Promise.resolve().then(() => console.log(2));
console.log(1);
// 1
// 2

Aynı Promise'e birden fazla then eklerseniz işleyiciler eklenme sırasıyla çalışır. Microtask ve task kuyruklarının ayrıntılı işleyişi için event loop yazımıza bakın.

Yeni yardımcılar: Promise.try ve Promise.withResolvers

Promise.try(func), verdiğiniz fonksiyonu senkron çağırır ve sonucu ne olursa olsun bir Promise'e çevirir. Fonksiyon senkron olarak hata fırlatsa bile bu hata reddedilmiş bir Promise olur; böylece senkron ve asenkron hataları tek bir catch ile ele alabilirsiniz.

Promise.withResolvers() ise bir nesne döndürür: promise, resolve ve reject. Promise'i oluşturduğunuz yerden farklı bir yerde çözmeniz gerektiğinde, executor dışına değişken taşıma alışkanlığının kısa yoludur.

javascript
Promise.try(() => {
  throw new Error('senkron hata');
}).catch((e) => console.log(e.message)); // senkron hata

const { promise, resolve } = Promise.withResolvers();
promise.then((v) => console.log('dışarıdan çözüldü:', v)); // dışarıdan çözüldü: 42
resolve(42);

Sık yapılan hatalar

Sık yapılan hatalar: 1. then içinde return unutmak ("floating" Promise), 2. Yakalanmayan reddedilmeler, 3. then'e Promise yerine sonucu geçirmek, 4. Promise'in iptal...

1. then içinde return unutmak ("floating" Promise)

Bir then işleyicisi içinde yeni bir Promise başlatıp onu döndürmezseniz zincir o işlemi beklemez ve sonraki adım undefined alır. MDN bu Promise'lere "floating" (başıboş) diyor; sonucunu ve hatasını izlemenin yolu kalmaz.

javascript
function kotu() {
  return Promise.resolve(1).then((x) => {
    Promise.resolve(x + 1).then((y) => console.log('içeride', y)); // return yok
  });
}
kotu().then((s) => console.log('sonuç:', s));
// içeride 2
// sonuç: undefined

Çözüm basit: işleyicide bir Promise ile karşılaştığınızda onu return edin ve işlenmesini bir sonraki then'e bırakın.

2. Yakalanmayan reddedilmeler

Hiçbir işleyicinin yakalamadığı bir ret, çalışma ortamına bildirilir. Tarayıcıda unhandledrejection olayı tetiklenir. Node.js'te ise process.on('unhandledRejection', ...) ile dinleyebilirsiniz; ancak MDN'in uyardığı gibi bu dinleyiciyi ekleyip içini boş bırakırsanız hatalar sessizce kaybolur. Her zincirin sonunda bir catch bulundurun.

3. then'e Promise yerine sonucu geçirmek

then bir fonksiyon bekler. then(islem()) yazarsanız islem hemen çalışır ve then'e onun sonucu gider; fonksiyon olmayan argüman yok sayılır. Doğrusu bir fonksiyon geçmektir.

4. Promise'in iptal edilebildiğini sanmak

Promise'in yerleşik bir iptal mekanizması yoktur. Altta yatan işlemi iptal etmek gerekiyorsa MDN, genellikle AbortController kullanılmasını önerir.

Sık Sorulan Sorular

Promise ile callback arasındaki temel fark nedir?

Callback'te sonucu işleyecek fonksiyonu çağrılan fonksiyona verirsiniz ve ne zaman, kaç kez çağrılacağı o fonksiyona kalır. Promise'te işleyiciyi döndürülen nesneye eklersiniz; dil, işleyicinin bir kez, asenkron olarak ve eklenme sırasıyla çalışacağını garanti eder.

Sonuçlanmış bir Promise'e sonradan then eklersem ne olur?

İşleyici yine çalışır. MDN'e göre işleyiciler, Promise siz onları eklemeden önce sonuçlanmış olsa bile çağrılır; yalnızca mevcut senkron kod bittikten sonra.

catch mi, then'in ikinci parametresi mi?

catch(f), then(null, f) ile aynıdır. Fark, konumdadır: zincirin sonundaki catch önceki tüm adımların hatalarını yakalar, then(a, b) içindeki b ise aynı then'deki a'nın fırlattığı hatayı yakalamaz.

Promise ile async/await arasında hangisini kullanmalıyım?

async/await Promise'lerin üzerine kurulu bir sözdizimidir; aynı eşzamanlılık davranışına sahiptir. Çoğu kodda await daha okunaklıdır ama birden fazla işlemi aynı anda başlatmak için yine Promise yöntemlerine ihtiyaç duyarsınız.

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 · async/await · Promise.all ve diğerleri · Event loop · Callback fonksiyon · Node.js nedir