Diyelim ki çok iyi bir sanatçısınız ve fanlarınız size sabah akşam ne zaman yeni şarkılarınızın geleceğini soruyor.
Siz de biraz rahatlamak için yeni şarkı yayınladığınızda onlara göndereceğinize söz verdiniz. Onlara bir liste verdiniz ve güncellemeleri buradan yayınlayacağınızı söylediniz. Böylece onlar da kendi email adreslerini yazar ve yeni şarkılar geldiğinde hemen bunları görebilir. Diyelimki bir şey yanış gitti ve yeni şarkıyı yayınlayamadınız bu şekliyle bile onlara bildirim gider.
Böylece herkes mutlu, sizi artık kimse darlamayacak, ve hiçbir yeni şarkınızı kaçırmayacaklar.
Bu programlamada karşılaştığımız olayların gerçek-hayattaki analojisi:
- Zaman alan “Kod üretme”. Örneğin ağ üzerinden veri yükleyen bir uygulama, yani “Şarkıcı”
- Üretilen kodu hazır olduğunda “tüketmek isteyen” kod. Birçok fonksiyon bu sonuca ihtiyaç duyabilir. Bu da “fanlar”'dır.
- promise(söz) bir çeşif özel JavaScript objesidir. Bu obje “üreten kod” ile “tüketen kod’u” birleştirir. Bizim kurduğumuz analoji’de bu “üyelik listesi”'ne denk gelir. “Kod üreten”'in ne kadar sürede üreteceği belli değildir. Bu söz hazır olduğunda tüm üyelere bunu bildirir.
Bu analoji tam olarak doğru değildir, aslında JavaScript promise’leri üyelik listesinden çok daha karmaşıktır: Bazı ek özellikleri ve sınırlılıkları mevcuttur. Fakat başlangıç olarak iyi diyebiliriz.
Promise objesinin yapıcı yazımı şu şekildedir:
let promise = new Promise(function(resolve, reject) {
// çalıştırıcı (üretici kod, "şarkıcı")
});
new Promise
’e gönderilen fonksiyona çalıştırıcı. Promise üretildiğinde, bu çalıştırıcı otomatik olarak başlar. Bu üretici kodu kapsar, sonrasında sonuç üretilir. Yukarıdaki analojiye göre: çalıştırıcı “şarkıcı”'dır.
Sonuçlanan promise
objesinin dahili özellikleri şu şekildedir:
durum
– ilk önce “bekleniyor (pending)” sonrasında “yerine getirildi” veya “red edildi” durumuna getirilir.sonuç
– ilk başlangıçtaundefined
’dır.
Çalıştırıcı işini bitirdiğinde, aşağıdaki fonksiyonları belirtilen argümanlar ile çağırmalıdır:
resolve(value)
— işin başarılı bir şekilde bittiğini belirtir:state
’i"fulfilled"
'e ayarlar,result
’ıvalue
’a ayarlar.
reject(error)
— bir hata olduğunu belirtir:state
’i"rejected"
'e ayarlar,result
’ıerror
’a ayarlar.
Sonra bu değişikliklerin “fanlara” nasıl bildirildiğini göreceğiz.
Aşağıda basit bir Promise yapıcısı ve “üretici kod”'lu bir çalıştırıcı göreceksiniz ( setTimeout
)
let promise = new Promise(function(resolve, reject) {
// Fonksiyon Promise oluşturulduğunda otomatik olarak başlar.
// Başladıktan bir sn sonra "done" yazarak işi bitirir.
setTimeout(() => resolve("done"), 1000);
});
Yukarıdaki kodun çalışması hakkında iki şey söyleyebiliriz:
- Çalıştırıcı otomatik olarak çağrıldı ve hemen başladı.
- Çalıştırıcı
resolve
vereject
adında iki argüman alır. Bu fonksiyonlar JavaScript motoru tarafından ön tanımlıdır. Bunları tekrar oluşturmaya gerek yok. Sadece hazır olduğunda çağırmamız yeterlidir.
“işliyor” durumundan bir sn sonra çalıştırıcı "resolve(“done”)`'ı çağırır ve sonucu üretir:
İşlem başarılı bir şekilde tamamlandığındna dolayı, “söz yerine getirildi”.
Aşağıda ise sözü hata ile reddeden bir çalıştırıcı örneği görülmektedir:
let promise = new Promise(function(resolve, reject) {
// çalışmaya başladıktan bir sn sonra iş hata ile sonuçlandı.
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
Özetlemek gerekirse çalıştırıcı ( bir süre alabilir ) işi bittikten sonra resolve
veya reject
’i çağırarak gerekli Promise objesinin durumunu değiştirir.
resolve
edilmiş veya reject
edilmiş Promise(Söz) objesine “yerleşmiş” denilir. Daha öncesinde ise bu durum “pending”(askıda) idi.
Çalıştırıcı sadece bir çözüm
veya bir red
’i çağırmalıdır. Söz’ün durumu değişikliği son olur.
Bundan sonraki her türlü çözüm
veya red
görmezden gelinir:
let promise = new Promise(function(resolve, reject) {
resolve("done");
reject(new Error("…")); // önemsenmez
setTimeout(() => resolve("…")); // önemsenmez
});
Buradaki fikir çalıştırıcının sadece bir tane sonuç veya bir tane hata dönmesi üzerinedir.
Ayrıca çözüm
/red
sadece bir tane (veya hiç) argüman kabul eder ve geri kalanlarını önemsemez.
Error
objesi ile reddetmeBazı durumlar beklenmediği gibi gidebilir. Böyle durumlarda reject
’i bir argüman ile çağırabiliriz. Error
objesini kullanmanız daha iyi olacaktır. Bunun nedeni ileride daha açık olacaktır.
çözüm
/reject
objelerinin çağırılmasıPrakikte, çalıştırıcı genelde asenkron çalışır ve çözüm
/red
’den bir tanesini bir süre sonra çağırır, aslında çağırmasa da olur. Bunun yerine doğrudan çözüm
veya redded
çağrılabilir. Örneğin:
let promise = new Promise(function(resolve, reject) {
// Hiç zaman almadan
resolve(123); // Anında sonucu bu şekilde verebiliriz
});
Bu durum işe başladığınızda fakat sonrasında değişen bir şey olmadığının görünüp hiç çalışmadan gönderilmek istendiğinde gerçekleştirilebilir.
Bu aslında iyi bir çözüm. Böylece söz hemen çözülmüş olur.
state
(durum) ve result
(sonuç) dahilidirPromise objesinin durum
ve sonuç
özellikleri dahilidir. Bundan dolayı “tüketici kod” içerisinden doğrudan erişemeyiz. Bunun yerine .then
/.catch
/.finally
gibi metodları kullanırız. Aşağıda bunlar açıklanmaktadır.
Tüketiciler: then, catch, finally
Promise(Söz objesi) çalıştırıcı(“üretici kod”, “şarkıcı”) ve tüketici(“fanlar”) arasında bir bağ oluşturur, bu sonuç veya hata objesi bekler. Tüketici fonksiyonlar .then
, .catch
ve .finally
ile kayıt olabilirler.
then
En önemli ve temel olan then
’dir.
Yazımı:
promise.then(
function(result) { /* başarılı bir sonucu işle */ },
function(error) { /* hayatı işle */ }
);
.then
’in ilk argümanı:
- Promise sonuca ulaştığında çalışır.
- Sonucu alır.
İkinci argümanı:
- Söz reddedildiğinde
- Hata alır.
Örneğin, aşağıdaki başarılı bir şekilde çözülen söz örneği:
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve("done!"), 1000);
});
// çözüm .then'in ilk fonksiyonunda çalışır.
promise.then(
result => alert(result), // 1 sn sonra "done!" ekrana basılır
error => alert(error) // çalışmaz
);
İlk fonksiyon çalıştı.
Red durumunda ikincisi çalışır:
let promise = new Promise(function(resolve, reject) {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
// red .then'in ikinci fonksiyonunda çalışır.
promise.then(
result => alert(result), // çalışmaz
error => alert(error) // "Error: Whoops!" 1 sn sonra ekrana basılır.
);
Sadece başarılı bir şekilde tamamlanması ile ilgileniyorsanız, .then
’e sadece bir tane fonksiyon vermeniz yeterlidir:
let promise = new Promise(resolve => {
setTimeout(() => resolve("done!"), 1000);
});
promise.then(alert); // 1 sn sonra "done!" ekrana basılır.
catch
Sadece hatalar ile ilgileniyorsanız, ilk argüman için null
kullanabilirsiniz: .then(null, errorHandlingFunction)
. Veya .catch(errorHandlingFunction)
'da kullanabilirsiniz, bu da şu şekilde olur:
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
// .catch(f) ile promise.then(null, f) aynıdır
promise.catch(alert); // 1 sn sonra "Error: Whoops!" ekrana basılır.
.catch(f)
ile .then(null, f)
aynı anlama gelmektedir. Birincisi sadece kısa yazım.
finally
try{...} catch {...}
'de finally
olduğu gibi sözlerde de finally
bulunmaktadır.
.finally(f)
çağrısı .then(f,f)
'ye benzemektedir. Söz yerine getirildiğinde, ister çözüm veya ret olsun, bu fonksiyon çalışır.
finally
temizlik için oldukça iyi bir işleyicidir. Örneğin yükleniyor belirtecinin durdurulması gibi. En nihayetinde olumlu veya olumsuz olarak söz tamamlanmıştır.
Aşağıdaki gibi:
new Promise((resolve, reject) => {
/* zaman alan bir iş yap ve çözüm/red'i çağır. */
})
// Söz herhangi bir şekilde tamamlandığında çalıştır.
.finally(() => stop loading indicator)
.then(result => show result, err => show error)
Aslında doğrudan then(f,f)
ile aynı diyemeyiz. Bazı önemli farklılıklar bulunmaktadır:
-
finally
işleyicisinin argümanı bulunmamaktadır.finally
bloğunda sözün başarılı veya başarısız olduğunu bilemeyiz. Bu bir problem değil çünkü en sonunda “genel” bir bitirme prosedürü gerçekleştirmek yeterlidir. -
finally
işleyicisi sonuç veya hata işleyicisine geçirgendirÖrneğin aşağıda
finally
’denthen
’e geçmiş bir sonuç görülmektedir:new Promise((resolve, reject) => { setTimeout(() => resolve("result"), 2000) }) .finally(() => alert("Promise ready")) .then(result => alert(result)); // <-- .then sonuçları işler
Burada ise sözde bir problem meydana gelmektedir,
finally
’dencatch
bloğuna geçmektedir:new Promise((resolve, reject) => { throw new Error("error"); }) .finally(() => alert("Promise ready")) .catch(err => alert(err)); // <-- .catch hata objesini işler.
Aslında bu çok uygun, çünkü
finally
içerisinde sözün sonucunu işleme gibi bir niyetimiz yok. Bunları üzerinden geçirse yeterli.Promiseler ve bunların zincirlemesi hakkında ilerleyen konularda daha derin bilgi verilecektir.
-
finally(f)
kullanmak yazım olarak.then(f,f)
'den daha uygundur çünküf
fonksiyonunu tekrar yazmanıza gerek kalmaz.
Eğer bir söz bekleme durumunda ise .then/catch/finally
işleyicileri sonuç için beklerler. Diğer türlü, söz bittiğinde, anında çalıştırılır:
// anında biten söz
let promise = new Promise(resolve => resolve("done!"));
promise.then(alert); // done! (hemen görünür)
.then
işleyicisi her türlü çalışır, söz zaman alsa da anında bitse de önemli değil.
Bir sonraki bölümde, sözlerin nasıl asenkron kod yazarken işimize yarayabileceği üzerinde duralım.
Örnek: loadScript
Bir önceki bölümden kodu yükleyen loadScript
kodunu alalım.
Aşağıda callback fonksiyonu ile yazılmış versiyonu hatırlama amaçlı aşağıya yazılmıştır:
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(null, script);
script.onerror = () => callback(new Error(`Script load error for ${src}`));
document.head.append(script);
}
Söz kullanarak bunları tekrar yazmaya çalışalım.
Yeni loadScript
fonksiyonu callback’e ihtiyaç duymayacaktır. Bunun yerine yüklenme tamamlandığında Promise objesi dönecektir. Dıştaki kod .then
kullanarak başka işleyiciler ekleyebilir:
function loadScript(src) {
return new Promise(function(resolve, reject) {
let script = document.createElement('script');
script.src = src;
script.onload = () => resolve(script);
script.onerror = () => reject(new Error(`Script load error for ${src}`));
document.head.append(script);
});
}
Kullanım:
let promise = loadScript("https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js");
promise.then(
script => alert(`${script.src} is loaded!`),
error => alert(`Error: ${error.message}`)
);
promise.then(script => alert('One more handler to do something else!'));
callback tarzı yazmadan daha iyi olan bir kaç özellik hemen görülebilir:
Promises | Callbacks |
---|---|
Söz ile işlemler doğal sırası dahilinde gerçekleşir. Önce loadScript(script) çalıştırılır, sonra then ile sonuç işlenir. |
loadScript çalışmadan önce sonuç ile ne yapılacağı bilinmelidir. |
.then fonksiyonunu bir sözde istediğimiz kadar kullanabiliriz. Her defasında listeye “yeni fan” eklenebilir.Bunun ile ilgili bir sonraki bölüme bakılabilir: Promise Zinciri. |
Sadece bir tane callback olmalı. |
Söz bize daha iyi bir akış ve esneklik sağlamaktadır.Bir sonraki bölümde diğer yararlarını da göreceğiz.