Modern Javascript'e [ES6] Giriş - Part 2

Modern Javascript, ES6, için ilk giriş dersini yaptıktan sonra Promises ve Async / Await konularıyla yazı serimize devam ediyoruz.

İlk part olan Modern Javascript'e ES6 Giriş - Part 1 yazısında let, const, arrow function, default parameters, for loop, spread attributes, maps, sets, static methods ve getter / setter başlıklarını öğrenmiştik. Şimdi ise ikinci part ile promises ve async / await başlıklarını inceleyeceğiz.

Öyleyse ilk başlığımız ile hızlıca başlayalım.


Promises

Promises, ES6'daki kullanışlı özelliklerden biridir. API isteği, dosya işleme, görüntü indirme vb. gibi zaman uyumsuz (async) işlemleri yapmak için kullanılırlar.

Peki, async nedir?

Async işlemler, tamamlanması biraz zaman alan işlemlerdir.

Örneğin, sunucuya API isteği yapan bir fonksiyon tanımladığınızı varsayalım. Bu fonksiyon hemen return değeri döndürmez. Sunucudan yanıt almak birkaç saniye sürer.

Bu nedenle, bu işlevi çağırıyorsanız ve değerini (yani) çıktısını bir değişkene atarsanız, undefined olacaktır. Çünkü Javascript, fonksiyonun bazı async işlemleri gerçekleştirdiğini bilmiyor.

Peki bununla nasıl başa çıkıyoruz? Ondan önce biraz tarih konuşalım.

Promises'den önce, programcılar callbacks tanımlardı. Callbacks, Javascript'te sync işlem tamamlandığında yürütülen normal işlevlerdir.

Örneğin, sunucuya bir API isteğinde bulunan bir işlev tanımlarsınız. Ardından, sunucudan yanıt aldığımızda yürütülecek bir callback fonksiyonu çalışır.

Yani yukarıdaki durumda, Javascript, API'den yanıt alana kadar yürütmeyi durdurmaz. Ve cevabı aldıktan sonra yürütülecek bir fonksiyon ( callback ) tanımlanır. Sanırım bu kısmı anladık.

Öyleyse, promises nedir?

Promises, async işlemlerin yapılmasına yardımcı olan nesnelerdir. Teknik olarak, async işlemin tamamlanmasını temsil eden nesnelerdir.

Bir promise'ın nasıl tanımlanacağını açıklamadan önce promise'ın yaşam döngüsünü açıklayalım.

Promise üç duruma sahiptir;

  1. Pending: Bu durumda promise, yalnızca async işlemi yürütür. Örneğin, sunucuya bazı API istekleri yapılırken veya bazı görüntüler cdn'den indirilirken bu durumdadır. Bu durumdan sonra promise, Fullfilled veya Rejected durumuna geçebilir.
  2. Fullfilled: Promise bu duruma ulaştıysa, async işlemin tamamlandığı ve çıktının elimizde olduğu anlamına gelir. Örneğin, API'den yanıt aldık.
  3. Rejected: Promise bu duruma ulaştıysa, async işlemin başarılı olmadığı ve işlemin başarısız olmasına neden olan hatamız olduğu anlamına gelir.

Tamam..Biraz kod görelim.

const apiCall = new Promise(function(resolve, reject) {
 // async operasyonlar burada tanımlanır..
});

Promise, new anahtar sözcüğü kullanılarak bir constructor oluşturularak tanımlanır. Daha sonra constructor'ın bir fonksiyonu olacaktır (buna executor function diyoruz.)

Async işlem, executor function içinde tanımlanır. Ve executor function iki parametreye sahip olabilir. Bunlar resolve ve reject'tir.

İlk parametre olan resolve aslında bir fonksiyondur. Executor function içinde çağrılır ve async işlemin başarılı olduğunu ve çıktıyı aldığımızı temsil eder. Resolve işlevi, promise'ın pending durumundan fullfilled durumuna geçmesine yardımcı olur.

Resolve gibi reject de bir fonksiyondur. Executor function içinde de çağrılır ve async işlemin başarısız olduğunu ve bir hatamızın olduğunu gösterir. Reject, promise'ın pending durumundan rejected durumuna geçmesine yardımcı olur.

const apiCall = new Promise(function(resolve, reject) {
 if ( API request to get some data ) {
  resolve("The request is successful and the response is "+ response);
 }
 else {
  reject("The request is not successful. The error is "+error);
 }
});

Yukarıdaki kodda, executor function içinde bazı async işlemler yaptığımızı görebilirsiniz. Daha sonra sunucudan yanıt alırsak resolve işlevi çağrılır. Ve eğer bir hata varsa, hata mesajı ile birlikte reject fonksiyonu çağrılır.

Promise tanımlamayı bitirdik. Promise'ın nasıl yürütüleceğini ve çıktının nasıl işleneceğini görelim.

// declare ettiğimiz promise'ı çağırma.
apiCall

İşte bu kadar. Yukarıdaki kodda, fonksiyon çağrılır ve promise verilir (yani) executor function yürütülür. Daha sonra çıktıya göre resolve veya reject fonksiyonu çağrılır.

Ancak promise'dan dönen çıktıyı işlemediğimizi görebilirsiniz.

Örneğin, API'den yanıt alırsak, yanıtı işlememiz gerekir. Ya da hatayı alırsak, düzgün bir şekilde ele almamız gerekir.

Peki bununla nasıl başa çıkıyoruz?

Promise'dan output almak için handlers kullanırız.

Handlers, yalnızca bir düğmeyi tıklamak, imleci hareket ettirmek vb. gibi bazı olaylar meydana geldiğinde yürütülen fonksiyonlardır.

Öyleyse, resolve veya reject işlevi çağrıldığında handle etmek için handlers kullanabiliriz.

Biraz kod görelim:

// promise'ı handlers ile çağırma.
apiCall.then(function(x) {console.log(x); })

Yukarıdaki kodda, promise'a bir handler olarak then fonksiyonunu ekledik. Handler olan then parametre olarak bir fonksiyon alır. Bu fonksiyon da parametre olarak x değişkeni alır.

Peki neler oluyor.

Handler olan then promise içinde resolve işlevi çağrıldığında meydana gelen olayı arar ve kendi fonksiyon parametresini çalıştırır (function(x)). Böylece yukarıdaki kodun çıktısı ise şu şekilde olur:

The request is successful and the response is {name: "Jon Snow"}

Aynı şekilde, catch isminde başka bir handler daha vardır. Bu handler ise reject fonksiyonunu yakalar.

Catch handler, reject işlevi çağrıldığında aldığı fonksiyon parametresini yürütür.

apiCall.then(function(x) {console.log(x); }).catch(function(x) {console.log(x); })

Async isteğin başarılı olmadığını varsayarsak (sözde rejectişlevi çağrılır) ve çıktımız şu şekilde olur:

The request is not successful. The error is {msg: "Unauthorized request"}

Bu kısmı bitirmeden kodumuza küçük bir refactoring yapalım.

apiCall
.then(function(x) {
 console.log(x); 
})
.catch(function(x) {
 console.log(x);
}) 

Şimdi özet olarak başlığımızın üzerinden bir geçelim;

  1. Promise, new anahtar kelimesi ve bir fonksiyon parametresi ile tanımlanır. Daha sonra parametresi olan fonksiyonun kendisi de, resolve ve reject olarak iki parametre alır.
  2. İşlem başarılı olduğunda resolve çağrılmalıdır.
  3. İşlem başarısız olduğunda reject çağrılmalıdır.
  4. Then handler resolve işlevine bakar.
  5. Catch handler reject işlevine bakar.
  6. Kodun okunabilirliğinden emin olun :)

Async / Await

Eğer promise konusunu anladıysanız, Async/Await oldukça kolay olacaktır. Ama promise konusunu anlamadıysanız, Async/Await konusu anlamanıza yardımcı olabilir. Belki siz de promise kullanmaktan net bir kaçış elde edebilirsiniz. :)

Async

Async anahtar sözcüğü, yalnızca promise'ları return etmek için herhangi bir işlev oluşturmakta görevlidir. Örneğin, aşağıdaki kodu göz önünde bulundurun.

async function hello() {
 return "Hello Promise..!"
}

hello() fonksiyonu bir promise döndürür. Yani yukarıdaki kod aşağıdaki koda eşdeğerdir.

function hello() {
 return new Promise(function(resolve, reject) {
  // executor function body.
 });
}

Artık promise yerine elimizde çok daha basit bir sytax olmuş oldu. Hemen yeni bir örneğe bakalım.

async function hello(a, b) {
 if (a < b) {
  return "Greater";
 }
 else {
  return new Error("Not Greater");
 }
}

Promise örneğinde olduğu gibi outputu yakalayacak then ve catch fonksiyonlarını yazalım.

hello(14, 10)
.then(function(x) {
 console.log("Good..! " + x); 
})
.catch(function(x) {
 console.log("Oops..! " + x); 
})

Eğer hello(4, 10) fonksiyonunu çağıracak olursak çıktımız şu şekilde olacaktır:

Good..! Greater

Yukarıdaki kodda, bir async işlevi tanımladık ve bir değer veya hata döndürdük. Error sınıfından ‘new’ ile bir hata döndürüyorsanız bu reject işlevine eşdeğerdir.

Async işlevin bir promise vereceğini unutmayın. Elbette, async işlevi içinde de resolve ve reject işlevini çağırabilirsiniz. Hadi bunun nasıl çalıştığını görelim.

async function Max(a, b) {
 if (a > b) {
  return Promise.resolve("Success");
 }
 else {
  return Promise.reject("Error");
 }
}

Max(4, 10)
.then(function(x) {
 console.log("Good " + x); 
})
.catch(function(x) {
 console.log("Oops " + x); 
});

Çıktısına bakarsak:

Oops Error

Await

Await, adından da anlaşılacağı gibi Javascript'in işlem bitene kadar beklemesini sağlar. await anahtar kelimesiyle bir API isteğinde bulunduğunuzu varsayalım. Endpointten yanıt alana kadar Javascript bekleyecek ve yanıt aldıktan sonra yürütmeye devam eder.

Tamam.. Daha derine inelim.

Await yalnızca async fonksiyonu içinde kullanılabilir. Async fonksiyonu dışında çalışmaz.

Bir örnek görelim

async function hello() {
 let response = await fetch('https://api.github.com/');
 // yukarıdaki satır, verilen API uç noktasından yanıtı alır.
 return response;
}

Fonksiyonu çağıralım.

hello()
.then(function(x) {
 console.log(x); 
});
...
...

Yukarıdaki kodda, API'den yanıt alırken await kullandığımızı görebilirsiniz. Getirme işleminin yanıtı alması birkaç saniye sürebilir, böylece yürütme durdurulup daha sonra devam ettirilir.

Await işleminin yalnızca hello fonksiyonu içindeki yürütmeyi durdurduğunu unutmayın. Hello işlevi dışında kalan kod bundan etkilenmeyecek ve durmayacaktır.


Modern Javascript yazı serimizin ikinci bölümünü de burada tamamladık. Üçüncü ve son bölüm olan part 3 de görüşmek üzere.