Photo by Thomas Q on Unsplash

JAVASCRIPT’IN TARIHÇESI

Promise-4 (Promise Kavramı)

Promise Nedir? Hangi ihtiyacı karşılamak için ortaya çıkmıştır ? Promise metodları Nelerdir ? async/await Nasıl Kullanılır?EventLoop da nasıl işletilir.

--

Bu yazıyı daha önceden yazmış olduğum Javascript’in Tarihçesi yazısının bir devamı olarak yazıyorum. Bir çok kavramı tek bir yazıda ele almanın yaratacağı karmaşıklıktan kaçmak için bu yönteme başvurdum. Bu yazılardaki amacım önceden Javascript’in varolan hangi özelliklerinin yetmediğini ve bu geliştirme(ES6) ile neyi hedeflediklerini anlatacağım.

Kısa bir dipnot olarak şunuda belirteyim ki, aslında bu konu teknik olarak İşletim Sistemi, Process, Thread, Concurrency, Non Blocking I/O, Async, Concurrency ve Paralellik ve Fonksiyonel programlama gibi bir çok konuyu dokunuyor. Derinlemesine teknik konularıda okumak için bir önceki yazımı Promise-1(Non-Blocking I/O)

Async mevcut yöntemlerle nasıl kullandığımızı Promise-2(Callback , Async, EventEmitter) yazımda anlattım.

Browser JS Nasıl İşletir? Stack, Heap, EventLoop, WebAPIs, CallbackQueue, RenderQueue Nasıl işler? sorularına cevap için Promise-3 (EventLoop) yazıma bakabilirsiniz.

Özetle hangi konuları anlatacağım?

  • Promise Nedir, Bu kavram ne zaman ortaya atıldı ?
  • Callback varken bunun üzerine birde Promise neden ekleyelim?
  • Promise Metodları
  • Promise EventLoop İçerisinde Nasıl Çalışıyor ?

1. Promise Nedir? Bu Kavram Ne Zaman Ortaya Atıldı?

Önceden Callback, Callback Hell çözmek için Async Kütüphanesi ve EventEmitter Observer örüntüsünden bahsetmiştim. Aslında burda da aynı sorun çözmeye çalışıyoruz. Birden fazla birbiri ardına asenkron işlemleri (Ajax, animasyon vb..) kodun okunabilirliğini bozmadan nasıl gerçekleştirebiliriz ? Bunları JQuery 1.8 den alınmış bir kaç örnek ile açıklamak istiyorum.

Yığın (Stacked): Aynı request birden fazla event handler eklemek istediğimizde.

const request = $.ajax(url);
request.done(()=>console.log('Request completed'));
request.done((retrievedData) => {
$('#contentPlaceholder').html(retrievedData);});

Paralel Görevler(Parallel Tasks): Birden fazla asenkron işlemin hepsinin tamamlanmasından sonra işlemlerini gerçekleştirmek istiyorsanız.

$.when(taskOne, taskTwo).done(()=>{console.log('taskOne and taskTwo are finished');});

Sıralı Görevler(Sequential Tasks): Asenkron işlemlerin sıralı(sequential) olarak işletilmesini gerçekleştirir. then sonrasında işlem başarılı bir şekilde resolved veya rejected olup olmama durumuna göre ilerletilir.

let step1, step2;
const url='http://fiddle.jshell.net';
step1 = $.ajax(url);
step2 = step1.then((data)=>{
let def = new $.Deferred();
setTimeout( ()=>{console.log('Request completed');
def.resolve();},2000);
return def.promise(); },
(err)=>{console.log('Step1 failed: Ajax request');});

Aslında concurrent(eş zamanlı) programlamada oluşan bu problemleri gidermek için ortaya future, promise, delay, and deferred gibi kavramlar 1976 — 1977 yıllarında ortaya atılmıştır. (Wikipedia)

  • Promise Kavramı (Daniel P. Friedman and David Wise and Peter Hibbard)
  • Future Kavramı (Henry Baker and Carl Hewitt)

Asenkron çalışmaya başladığında gelecekte işlemin nasıl işletileceği ve sonucunun nasıl sonuçlanacağını bilemeyiz.

  • promise gelecekteki bilinmeyen değeri temsil eder.
  • deferred çalışması sonlanmamış devam eden işi temsil eder.

Her bir deferred için bir promise bulunmaktadır. Promise fonksiyonları (future) değerine erişim için proxy görevi gerçekleştirir. JQuery yapısında İşlem gerçekleştiğinde işlem çözümleyicisi $.Deferred nesnesinin resolved/rejected fonksiyonları çağrılarak Promise Nesnesini işlem çözümleyicisinden ayrılmasını sağlar. Promise bize gelecek ile sunulan bir tekliftir. Ve bu teklifin özellikleri (API ve davranış özellikleri) aşağıdaki gibi olmalıdır.

  • Tek bir işlemden dönen nihai değerdir. (Birden fazla çalıştırılması söz konusu değildir)
  • 3 state(durumdan) birinde olabilir
Fulfilled: Ne zamanki işlem başarılı bir şekilde gerçekleşmiş resolved()Rejected/Failed: Ne zamanki işlem başarız bir şekilde gerçekleşmiş rejected()Pending/Unfilled: işlem devam etmekte
  • promise.then her zaman başka bir promise nesnesi dönmelidir. Böylece thenable yapılarla then(bundan sonra) zincirleri oluşturulabilir.
  • then(onResolveHandler, onRejectHandler) fonksiyonu içerisine dönen sonuçlar yakalanır.
Promise State Değişimleri
  • Promise yan efektlerden etkilenmemesi (avoid side effects)için değeri değiştirilemez veya Promise birden fazla değişkene atanamaz. Immutable ’dır. (Bu konunun biraz daha derinlerine inmek için Oğuz Kılıç’ın Javascriptte Değişmezlik) yazısını okumanızı öneririm.

Sonuçta ES6 Öncesinde Promise kavramı farklı farklı kütüphanelerde ele alınıyordu. Bu spec (Promises/A Proposal & Promises/A+ Spec) sayfalarında bulabilirsiniz. Buna göre geliştirilmiş kütüphanelerden bazıları (When.js, Q.js, RSVP, jQuery)’dır

2. Callback Varken Bunun Üzerine Birde Promise Neden Ekleyelim?

Burdan şu anlaşılmasın ben Asenkron yapan her işlemde Promise kullanmalıyım. Promise bizim karmaşık asenkron zincirlerini yönetme sırasında esas karşımıza çıkıyor. Birde Callback yönetilmesi konusunda hiç bir protokol ve spec olmaması. Callback çağrımları bir kontrol dahilinde gerçekleşmeyebilir. Sorumluluk bunu çağıran yerdedir. İşlem bitmeden çağırabilir, işlem bittikten sonra çağırmayabilir, birden fazla çağırabilir, sonuç değeri değişebilir. Hata durumlarını yakalamada problemler yaşanabilir.

Eğer bu tipte sorunlar ile karşılaşmak istemiyorsanız. Callback yapınızın üzerine Promise yapılarınızı eklemenizde fayda var. Burda bir diğer konuda ES8 ile birlikte gelen async/await kavramı bu konuyu ilerde daha detaylı anlatacak olsamda şu anda özetle Callback → Promise → async/await geçişinin nasıl olduğunu kodla biraz anlatmak istiyorum.

Callback

CallbackHell yapısının oluşmasının ne kadar kolay olduğunu görebilirsiniz. Kodun okunabilirliğini çok kısa sürede kaybedebilirsiniz.

const waitThenCall = (callback) => {setTimeout(() => { callback() }, 1000)}waitThenCall(() => {console.log("Say Hello1");
waitThenCall(() => {console.log("Say Hello2");
waitThenCall(() => { console.log("Say Hello3");})})})

Promise

Promise yapısı ile CallBack Hell yapısından then zinciri sayesinde kurtulabiliyoruz aynı zamanda err durumlarınıda handle etme şansımız oluyor. Aynı zamanda tüm process err state default handle etmesi için catch bloğuda bulunmaktadır.

const waitThenCall = (msg) => { 
return new Promise((resolve, reject) => {
setTimeout(() => {resolve(msg + “success”) }, 1000)});}
waitThenCall(“Hello1_”)
.then((data) => { console.log(data)
return waitThenCall(“Hello2_”)}, (err)=>{})
.then((data) => { console.log(data)
return waitThenCall(“Hello3_”)}, undefined)
.then((data) => { console.log(data)})
.catch(err) => {console.log(err+"X")})

Async/Await

Aslında bu ileride bahsedeceğim bir konu ama özetle ES8 ile birlikte gelen async/await ile bu then yerine normal senkron bir kod gibi kodunuzu yazabilirsiniz. Bu sefer koda tag koyuyoruz. Bak bu fonksiyon async , bu call asenkron burda await ile bekle gibisinden. try — catch mekanizmasınıda direk kullanabilirsiniz.

const waitThenCall = (msg) => { 
return new Promise((resolve, reject) => {
setTimeout(() => { resolve(msg + "success") }, 2000)});}
async function run(){ try{
const result=await waitThenCall("Hello1");
console.log(result)
const result2=await waitThenCall("Hello2");
console.log(result2)
console.log("Hello")
}catch(e){
console.log(e);
}
run(); //Result
//Hello1success
//Hello2success
//Hello

3. Promise Metodları

Promise prototype bağlı (then, catch, finally) metodlarında then() promise zincirleri oluşturmanızı sağlayan bağlaç görevi üstlenir. catch() reject sırasında ele alınmamış hataları yakalamanızı sağlar. finally() promise en sonunda başarılı/başarısız tamamlandığında bu metod çağrılır.

Promise işleminin başarılı/başarısız tamamlanması ardından (resolve, rejected) metodları çağrılır. Promise.resolve(value) , Promise.reject(err) gibi.

Birden fazla Promise koşullarını yöneten metodlar (all, race, allSettled)

  • all: Tüm Promise başarıyla tamamlanmasını bekler.
  • race: İçlerinden en önce hangi Promise tamamlanırsa onun sonucunu alır.
  • allSettled: Tüm Promise başarılı, başarısız işletimleri bitince sonuçlarını status leri ile birlikte geriye döner.
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
setTimeout(() => { resolve("success") }, 1000)});
const promise4 = new Promise((resolve, reject) => {setTimeout(() =>{ reject("fail") }, 1000)});Promise.then([promise1, promise2, promise3]).
then(function(values) { console.log(values);});
//Output ->3
Promise.all([promise1, promise2, promise3]).
then(function(values) { console.log(values);});
//Output ->[3,42,"success"]
Promise.allSettled([promise1, promise2, promise4]).
then(function(values) { console.log(values);});
//Output ->[{value:3,status:"fullfilled"},{value:42,status:"fullfilled"},{value:"success",status:"rejected"]

4. Promise EventLoop İçerisinde Nasıl Çalışıyor ?

Bir önceki yazımda EventLoop’ların nasıl çalıştığını anlatmıştım. Fakat Promise bu mekanizma içerisinde nasıl çalıştığını anlatmamıştım. Bu kısımda bunu biraz açmak istiyorum.

Aşağıdaki resimde sol tarafta önceden anlatmış olduğum Callback/TaskQueue bulunuyor. Promise ile birlikte buna MicroTask Queue(Kuyruğunun) eklendiğini görebilirsiniz.

EventLoop + MicroTask Queue

Promise MicroTask kuyruğunda ele alınır ve Task kuyruğundan önce işletilir. Jake Archibald yazısındaki örnekler üzerinden giderek konu daha iyi anlatılabilir.

Aşağıda örnekte görüleceği gibi Promise kodları setTimeout task daha önce çağrılmıştır.

console.log('script start');//Task
setTimeout(function() { console.log('setTimeout');}, 0);
//MicroTask
Promise.resolve().then(function() {
console.log('promise1'); }).
then(function() {
console.log('promise2');
});
console.log('script end');//Output
//'script start', 'script end', 'promise1', 'promise2','setTimeout'

Promise mevcut çalışan script üzerinde işletilmeye başlar. Then sonrasındaki callback Microtask kuyruğu üzerinden işletilir ve bu arka arkaya işletilen Microtask Queue doluysa ve zincir devam ediyorsa bu then zincirinin arasına TaskQueue girmesine izin verilmez. Yukarıdaki örnekte Promise1 , Promise2 nin arasına timeout girmediği gibi.

References

Okumaya Devam Et 😃

Bu yazının devamı veya yazı grubundaki diğer yazılara erişmek için bu linke tıklayabilirsiniz.

--

--