Callback fonksiyonlarına giriÅ bölümünde bahsettiÄimiz probleme tekrar göz atalım. Burada bir seri asenkron görevin ardaÅık bir biçimde çaÄırılması gerekmekte. ÃrneÄin script dosyalarının yüklenmesi. Bunu Promise ile nasıl yapabiliriz?
Promise bize bunu gerçekleÅtirebilmemiz için bazı yöntemler sunmakta.
Bu bölümde Promise Zinciriânden bahsedeceÄiz.
Åöyle:
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
Buradaki yaklaÅım, elde edilen sonuçların .then zincirinde tekrar iÅleme alınmasıdır.
Genel akıŠÅu Åekilde:
- İlk Promise 1 saniye içerisinde sonuçlanır
(*), - Sonrasında ilk
.theniÅleyicisi çaÄrılır(**). - Bu fonksiyondan dönen deÄer bir sonraki
.theniÅleyicisine aktarılır(***) - â¦ve zincirleme süreç böyle iÅlemeye devam eder.
Sonucun iÅleyiciler arasında aktarılmasıyla birlikte alert fonsiyonlarının çaÄırıldıÄını ve sırasıyla 1 â 2 â 4 çıktılarını verdiÄini görürüz.
promise.then çaÄrısı bir Promise döndürür, böylelikle bir sonraki .then iÅleyicisi çaÄırılabilir.
İÅleyiciden bir deÄer döndüÄünde, bu Promiseâ in sonucu olur. Böylece bir sonraki .then iÅleyicisi bu deÄer ile çaÄrılır.
Promise Zinciri Åöyle baÅlamakta:
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result);
return result * 2; // <-- (1)
}) // <-- (2)
// .thenâ¦
.then iÅleyicisinden dönen deÄer yine Promiseâ dir, böylece zinciri oluÅturacak olan diÄer .then iÅleyicileri (1) ve (2) deÄerleri üzerinden çaÄrılabilmektedir.
AÅaÄıda genel olarak yapılan bir hata görülmekte. TanımlanmıŠolan Promise objesinin bir deÄiÅkene atanıp bunun üzerinden tekil Åekilde .then iÅleyicisinin çaÄırılması bir Promise Zinciri oluÅturmaz.
Ãrnek:
let promise = new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
promise.then(function(result) {
alert(result); // 1
return result * 2;
});
Burada tek bir Promise objesine birden fazla iÅleyicinin eklenmesi ve bu iÅleyicilerin birbirleri arasında veriyi aktarmadan, yanlızca ilk dönütü birbirlerinden baÄımsız olarak iÅledikleri görülmektedir.
Burada durumu niteleyen akıÅı görebiliriz (bunu yukarıdaki zincir akıÅını göz önüne alarak inceleyiniz):
Aynı Promise üzerindeki tüm .then iÅleyicileri yukarıdaki örnekte aynı sonucu vermekte. Yani alert fonksiyonu sürekli olarak 1 deÄerini gösterir.
Genel olarak uygulamalarımızda bir Promise üzerinde birden fazla iÅleyiciye nadiren ihtiyaç duyulur. Fakat, zincir yapısı ise çok daha sık Åekilde kullanılmaktadır.
Promise Dönütü
Normal koÅullarda .then iÅleyicisinden dönen deÄer doÄrudan sonraki iÅleyiciye bir parametre olarak aktarılır. Fakat burada bir istisna var.
Åayet dönen deÄer yine bir Promise ise zincirleme akıŠbu Promise sonuçlanana dek durur, yeni deÄerin gelmesini bekler. Sonrasında gelen dönüt bir sonraki .then iÅleyicisine aktarılır.
Ãrnek:
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
}).then(function(result) {
alert(result); // 1
return new Promise((resolve, reject) => { // (*)
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) { // (**)
alert(result); // 2
return new Promise((resolve, reject) => {
setTimeout(() => resolve(result * 2), 1000);
});
}).then(function(result) {
alert(result); // 4
});
Buradaki ilk .then iÅleyicisi alert fonksiyonu ile 1 deÄerini gösterir ve yeni bir Promise objesi oluÅturarak döndürür. Bir sonraki .then iÅleyicisi result parametresi olarak gönderdiÄimiz Promiseâ i alır. 1000ms sonrasında bu Promise sonuçlanır ve kendi tanım aralıÄındaki result deÄerini kullanarak result * 2 deÄerini döner.(**) satırında tanımlanmıŠolan iÅleyici içerisindeki alert fonksiyonu çalıÅır ve 2 deÄerini gösterir.
Sonuç olarak alert fonksiyonları birer saniyelik gecikmeyle birlikte sırasıyla 1 â 2 â 4 çıktılarını gösterir.
Promise dönütü oluÅturabiliyo olmak bize asenkron zincir yapıları oluÅturma kolaylıÄı saÄlar.
Ãrnek: loadScript
Yukarıda bahsettiÄimiz zincir yapısını, önceki bölümlerde tanımladıÄımız previous chapter örneÄindeki içeriÄi kullanmak için tanımlayalım:
loadScript("/article/promise-chaining/one.js")
.then(function(script) {
return loadScript("/article/promise-chaining/two.js");
})
.then(function(script) {
return loadScript("/article/promise-chaining/three.js");
})
.then(function(script) {
// scriptlerde tanımlı fonksiyonların
// yüklendiÄini doÄrulamak için çaÄıralım
one();
two();
three();
});
Arrow fonksiyon notasyonu kullanılarak daha kısa bir biçimde Åöyle de yazılabilir:
loadScript("/article/promise-chaining/one.js")
.then(script => loadScript("/article/promise-chaining/two.js"))
.then(script => loadScript("/article/promise-chaining/three.js"))
.then(script => {
// scriptler yüklendi, fonksiyonlar artık çaÄrılabilir
one();
two();
three();
});
Burada her loadScript çaÄrısı bir Promise döndürmekte ve takip eden .then iÅleyicisi bu Promise deÄeri ortaya çıktıÄında çalıÅmakta. Böylelikle scriptler birbiri ardına yüklenebilmekte.
Yukarıdaki kod bloÄunun halen sade bir yapı halinde olduÄuna da dikkat etmekte fayda var. Kod bloÄu saÄ tarafa doÄru deÄil aÅaÄı yönde geniÅleme göstermekte. Burada herhangi bir Åekilde âkıyamet piramidi / callback cehennemiâ yapısının oluÅmadıÄını görebiliriz.
.then iÅleyicisini doÄrudan loadScript fonksiyonu üzerinden de çaÄırabiliriz:
loadScript("/article/promise-chaining/one.js").then(script1 => {
loadScript("/article/promise-chaining/two.js").then(script2 => {
loadScript("/article/promise-chaining/three.js").then(script3 => {
// script1, script2 ve script3 parametrelerine eriÅilebilir
one();
two();
three();
});
});
});
Burada yukarıda bahsettiÄimiz büyüme yönü bu kod bloÄunda saÄa doÄru olmakta. Yani callback ile yaÅadıÄımız sorunlar burada da oluÅmaya baÅlayacaktır.
Promise yapısını yeni kullanmaya baÅlayan kiÅiler bazen zincir yapısı hakkında fikir sahibi olmadıkları için yukarıdaki kod bloÄuna benzer bir yapı kurabilirler. Fakat bu da kodun okunabilirliÄini/sürdürülebilirliÄini azaltmaktadır. Bundan dolayı zincir yapısının kullanımı tercih edilmelidir.
Fakat bunun da istisnai olarak kullanılması gereken durumlar ortaya çıkabilmektedir. ÃrneÄin; script1, script2 ve script3 parametrelerine en içteki iÅleyiciden eriÅilmesi gereken bir durumun oluÅabilmesi gibi.
.then iÅleyicisi herhangi bir âthenableâ obje döndürebilir ve bu, aynı bir Promise objesi gibi iÅlem görür.
Bir âthenableâ objesi, üzerinde .then metodu tanımlı herhangi bir objedir.
Buradaki düÅünce, 3. parti kütüphanelere kendi Promise uyumlu objelerini geliÅtirebilme esnekliÄi sunmaktır. Bu kütüphaneler kendi istekleri doÄrultusunda farklı metodları objelerine ekleyebilirler.
AÅaÄıda bir âthenableâ obje örneÄi mevcut:
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve); // function() { native code }
// this.num*2 deÄerini 1 saniye sonra çözümle
setTimeout(() => resolve(this.num * 2), 1000); // (**)
}
}
new Promise(resolve => resolve(1))
.then(result => {
return new Thenable(result); // (*)
})
.then(alert); // 2 deÄerini 1000ms sonra gösterir
JavaScript (*) olan satırda .then tarafından dönen objeyi kontrol eder: eÄer bu obje üzerinde .then metodu tanımlıysa resolve ve reject fonksiyonlarını parametre olarak .then fonksiyonuna saÄlar ve bu metodlardan biri çaÄırılıncaya dek bekler.
Yukarıdaki örnekte resolve(2) metodu (**) satırı yorumlandıktan 1 saniye sonra çaÄrılır. Ortaya çıkan sonuç sonrasında Promise Zinciri üzerinde aÅaÄı doÄru gidecektir.
Bu özellik sayesinde Promise Zinciri özelliÄine sahip objelerin yaratımı Promise objesinden kalıtılmak zorunda olmaksızın yapılabilir.
Daha kapsamlı bir örnek: fetch
Ãn-yüz programla içinde promise çoÄunlukla aÄ Ã¼zerinde yapılan isteklerde kullanılır. Bunu gerçekleÅtiren bir örnekle devam edelim.
Burada fetch metodunu uzak bir saÄlayıcıdan veri almak için kullanacaÄız. Bu metod birçok parametreye sahip fakat basitçe kullanımı aÅaÄıdaki gibi:
let promise = fetch(url);
Bu, verilmiÅ olan url adresine bir istek yollar ve Promise döner. Promise, uzaktaki saÄlayıcıdan istenen veriyi aldıÄında bir response objesi olarak çözümlenir.
Tüm dönütü elde edebilmek için response.text() metodunu çaÄırmamız gerekir. Bu çaÄrı, uzak sunucudan tüm içerik alındıktan sonra çözümlenecek olan bir Promise döner.
AÅaÄıdaki örnekte user.json dosyasına çaÄrı yapılmakta ve dönüt alert fonksiyonu ile gösterilmekte:
fetch('/article/promise-chaining/user.json')
// uzak sunucudan cevap geldiÄinde aÅaÄıdaki `.then` çalıÅır
.then(function(response) {
// response.text() tüm içeriÄi çözümleyecek olan promise' i döner
// sonrasında içeriÄi indirir
return response.text();
})
.then(function(text) {
// ...ve uzak sunucudan gelen içerik
alert(text); // {"name": "iliakan", isAdmin: true}
});
Ayrıca response.json() metodu gelen veriyi doÄruca JSON formatına çözümler. Ãrnekteki senaryoya daha uygun olduÄundan kodda bu kısmı deÄiÅtirelim.
Arrow fonksiyon kullanarak da daha sade bir biçime ulaÅabilmek için kodu tekrar düzenleyelim.
// yukarıdan farklı olarak response.json() kullanıyoruz
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => alert(user.name)); // iliakan
Åimdi de elde ettiÄimiz kullanıcı verisiyle bir Åeyler yapalım.
ÃrneÄin, Github sayfasına da bir istekte bulunarak kullanıcı profilini ve avatarını elde edebiliriz.
// user.json dosyasına istek yolla
fetch('/article/promise-chaining/user.json')
// json formatına dönüÅtür
.then(response => response.json())
// GitHub sayfasına isteÄi yolla
.then(user => fetch(`https://api.github.com/users/${user.name}`))
// dönütü json formatına dönüÅtür
.then(response => response.json())
// kullanıcı avatar resmini (githubUser.avatar_url) 3 saniye boyunca göster
.then(githubUser => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => img.remove(), 3000); // (*)
});
Yukarıdaki kod bloÄu istediÄimiz Åekilde çalıÅmakta fakat burada potansiyel bir sorun da mevcut.
(*) iÅaretli satıra bakıcak olursak, gösterdiÄimiz avatar resmi DOM üzerinden silindikten sonra herhangi bir Åey yapmak istiyorsak bunu nasıl yapabiliriz? ÃrneÄin sayfa üzerde bu kullanıcının verilerini düzenlemek için bir form göstermek istiyor olalım. Mevcut durumda bunu yapamayız, çünkü son .then iÅleyicisi herhangi bir deÄer döndürmemekte. Bundan dolayı, tekrar .then fonksiyonu çaÄırılamaz.
Mevcut zinciri geniÅletmek için bu iÅlem sonucunda bir promise döndürmemiz gerekir.
Åöyle:
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(githubUser => new Promise(function(resolve, reject) {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
}))
// triggers after 3 seconds
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
Yapılan deÄiÅiklikten sonra img.remove() metodu ayn Åekilde 3 saniye sonra çalıÅacak. Fakat, sonrasında resolve(githubUser) ile kullanıcı bilgilerini bir sonraki zincirde kullanılabilmesi için çözümleyecek.
Genel bir kural olarak, asenkron eylemler her zaman bir promise döndürmelidir.
Bu, asenkron eylemlerden sonra gerçekleÅtirilecek iÅlemler için olanak saÄlamaktadır. Åu anda, zincirin son iÅleyicisinden sonra herhangi bir geniÅlemeye ihtiyaç duymuyor olsak dahi, ileriye dönük olarak buna ihtiyacımız olabileceÄini göz önüne almamız gerekir.
Nihayetinde, kodu daha yönetilebilir parçalara ayrıÅtırarak tekrar kullanılabilir fonksiyonlarımızı yazıyoruz:
function loadJson(url) {
return fetch(url)
.then(response => response.json());
}
function loadGithubUser(name) {
return fetch(`https://api.github.com/users/${name}`)
.then(response => response.json());
}
function showAvatar(githubUser) {
return new Promise(function(resolve, reject) {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
});
}
// fonksiyonları kullanıyoruz:
loadJson('/article/promise-chaining/user.json')
.then(user => loadGithubUser(user.name))
.then(showAvatar)
.then(githubUser => alert(`Finished showing ${githubUser.name}`));
// ...
Ãzet
EÄer .then (ya da catch/finally) iÅleyicisi promise dönerse, zincirleme akıŠbu Promise sonuçlanana dek durur, yeni deÄerin gelmesini bekler. Promise çözümlendiÄinde akıŠkaldıÄı yerden yeni deÄer ile devam eder.
Genel bakıÅ:
Yorumlar
<code>kullanınız, birkaç satır eklemek için ise<pre>kullanın. EÄer 10 satırdan fazla kod ekleyecekseniz plnkr kullanabilirsiniz)