Callback, promises ve diÄer soyut kavramları göstermek için belirli tarayıcı yöntemlerini kullanacaÄız: özellikle, betikleri yüklemek ve basit belge manipülasyonları gerçekleÅtirmek.
Bu yöntemlerle alıÅık deÄilseniz ve örneklerdeki kullanımı anlamakta zorlanıyorsanız, sonraki bölümden birkaç bölümü okumanız faydalı olabilir.
Yine de, her Åeyi mümkün olduÄunca açık hale getirmeye çalıÅacaÄız. Tarayıcı yöntemleri ve iÅlemleri ile ilgili gerçekten karmaÅık bir Åey olmayacak.
JavaScriptâin çalıÅtıÄı geliÅtirme ortamları tarafından saÄlanan birçok fonksiyon, asenkron eylemleri planlamanıza olanak tanır. BaÅka bir deyiÅle, Åu an baÅlattıÄımız ancak daha sonra tamamlanan eylemler.
ÃrneÄin, setTimeout fonksiyonu, bu türden bir fonksiyona örnektir.
Asenkron eylemlerin diÄer gerçek dünya örnekleri de bulunmaktadır, örneÄin, betik ve modülleri yükleme (bunları daha sonraki bölümlerde ele alacaÄız).
İÅte loadScript(src) adlı fonksiyonu inceleyin; bu fonksiyon, verilen src ile bir betiÄi yükler:
function loadScript(src) {
let script = document.createElement('script');
script.src = src;
document.head.append(script);
}
Bu fonksiyonun amacı yeni kodu yüklemektir. <script src="...">'yi dökümana ekler ve çalıÅtırır.
AÅaÄıdaki gibi kullanılabilir.
// kodu yükler ve çalıÅtırır.
loadScript('/my/script.js');
Bu fonksiyon âasenkronâ olarak adlandırılır, çünkü iÅlerini hemen deÄil de daha sonra bitirir.
ÃaÄrı ile script yüklenmeye baÅlar ve sonrasında çalıÅtırılır. Yüklerken aÅaÄıdaki kod çalıÅmayı bitirebilir ve eÄer bu yükleme zaman alırsa aynı anda diÄer kodlar da çalıÅabilir.
loadScript('/my/script.js');
// loadScript altındaki kodlar loadScript'in bitmesini beklemeden çalıÅmaktadır.
// ...
Diyelim ki kod yüklendikten sonra yeni kodu kullanmak istiyor olalım. Yeni fonksiyonlar yaratılmıÅsa bunları kullanacaÄımızı varsayalım.
EÄer bunu doÄrudan loadScript(â¦) çaÄrısı sonrasına yaparsanız çalıÅmaz:
loadScript('/my/script.js'); // "function newFunction() {â¦}" a sahip olduÄunu varsayalım
newFunction(); // böyle bir fonksiyon bulunmamaktadır.
DoÄal olarak, tarayıcı kodu yükleyecek zaman bulamadı. Bundan dolayı doÄrudan yeni fonksiyonu çaÄırdıÄında hata meydana geldi. Bundan sonra loadScript fonksiyonu yüklemenin ne durumda olduÄunu bildiremez. Script en nihayetinde yüklenir ve sonrasında çalıÅtırılır, bu kadar. Fakat biz bunun ne zaman olduÄunu bilmek istiyoruz. Yüklenen koddaki fonksiyonlar ve deÄiÅkenleri kullanmak istiyoruz.
callback fonksiyonunu ikinci bir parametre olarak loadScript e ekleyelim, bu kod yüklendiÄinde çalıÅması lazım.
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(script);
document.head.append(script);
}
EÄer kod içerisindeki bir fonksiyonu çaÄırmak istiyorsak, callback içerisine yazmalıyız:
loadScript('/my/script.js', function() {
// callback kod yüklendikten sonra çalıÅacaktır.
newFunction(); // artık çalıÅır.
...
});
Fikir: ikinci argüman bir fonksiyondur (genelde isimsiz ) ve eylem tamamlandıktan sonra çalıÅır.
AÅaÄıda kodun çalıÅtırılabilir hali bulunmaktadır:
function loadScript(src, callback) {
let script = document.createElement('script');
script.src = src;
script.onload = () => callback(script);
document.head.append(script);
}
loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => {
alert(`Cool, the ${script.src} is loaded`);
alert( _ ); // yüklenmiŠkodlar içerisinde bu fonksiyon tanımlı.
});
Buna âcallback-tabanlıâ asenkron programlama tipi denir. Bir fonksiyon asenkron olarak bir iÅ yapıyorsa callbackâi de sunmalıdır. Böylece bundan sonra neyin çalıÅacaÄına karar verebiliriz.
Burada loadScript için kullandık, fakat bu genel bir yaklaÅımdır.
Callback içinde callback
Aynı anda iki kod parçasını sıralı olarak nasıl yükleyebiliriz: ilk önce birincisini, bittikten sonra ikincisini.
DoÄal olan ikinci loadScriptâi callback içine aÅaÄıdaki gibi koymaktır:
loadScript('/my/script.js', function(script) {
alert(`Cool, the ${script.src} is loaded, let's load one more`);
loadScript('/my/script2.js', function(script) {
alert(`Cool, the second script is loaded`);
});
});
DıÅtaki loadScript tamamlandıktan sonra, içteki çalıÅmaya baÅlar.
EÄer bir tane daha istersek â¦?
loadScript('/my/script.js', function(script) {
loadScript('/my/script2.js', function(script) {
loadScript('/my/script3.js', function(script) {
// ...tüm kodlar yüklendikten sonra devam eder.
});
})
});
Böylece, her yeni eylem callback içerisinde kalır. Bu birkaç aksiyon için sorun olmaz fakat daha çok ise sorun yaratacaktır.
Hataları İÅlemek
Yukarıdaki örnekte hataları düÅünmedik. Ya kod hata verirse? Callback fonksiyonu buna göre hareket edebilmelidir.
AÅaÄıda loadScriptâin hataları takip eden, geliÅtirilmiÅ versiyonu yer almaktadı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);
}
EÄer baÅarılı bir Åekilde çalıÅırsa callback(null, script), hata alırsa callback(error) çaÄırılır.
Kullanımı:
loadScript('/my/script.js', function(error, script) {
if (error) {
// handle error
} else {
// script loaded successfully
}
});
Yine bu yöntemin genel bir kullanım olduÄunu söyleyebiliriz. Buna âerror-first callbackâ stili denilmektedir.
Düzen Åu Åekildedir:
callbackâin ilk argümanı hata için ayrılır. Sonracallback(err)çaÄırılır.- İkinci argüman ise baÅarılı bir sonuçta gönderilir. Sonra
callback(null, result1, result2...)çaÄırılır.
Böylece tek bir callback fonksiyonu ile hem hata gönderilebilir, hem de cevap dönülebilir.
Kıyamet pramidi
İlk bakıldıÄında asenkron kodlama mantıklı gelebilir. Gerçekten de öyle. Bir veya iki çaÄrı fena görünmüyor.
Fakat birden çok asenkron iÅ için kod aÅaÄıdaki gibi olacaktır:
loadScript('1.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('2.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('3.js', function(error, script) {
if (error) {
handleError(error);
} else {
// ...tüm kodlar yüklendikten sonra devam et (*)
}
});
}
})
}
});
Yukarıdaki kodda:
- Ãnce
1.jsâyi yükledik. - Hata yoksa
2.jsâyi yükle. - Hata yoksa
3.jsâyi ve en sonda da(*)çalıÅtırılır.
ÃaÄrılar çoÄaldıkça kod daha derinlere inmekte ve bunun yönetimi de zorlaÅmaktadır, özellikle içerisinde ... yerine gerçek kod varsa bu birçok döngüye, koÅula sahip olacaktır.
Bunun için âcallback cehennemiâ veya âKıyamet piramidiâ denilebilir.
âPiramitâ her bir çaÄrıda saÄa doÄru büyüyecek ve kontrolden çıkacaktır.
Bu Åekliyle kodlamak pek de iyi görünmemekte.
Bunu her çaÄrıyı ayrı birer fonksiyon yaparak çözmeye çalıÅırsak:
loadScript('1.js', step1);
function step1(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('2.js', step2);
}
}
function step2(error, script) {
if (error) {
handleError(error);
} else {
// ...
loadScript('3.js', step3);
}
}
function step3(error, script) {
if (error) {
handleError(error);
} else {
// ...continue after all scripts are loaded (*)
}
};
GördüÄünüz gibi aynısı, fakat iç içe yazılmıŠderinlemesine bir fonksiyon yok. Her iÅ ayrı bir fonksiyonda tamamlanıyor.
Tamamdır. Artık çalıÅıyor fakat ayrı ayrı bir tablo gibi duruyor. Okuması oldukça zor, sizin de fark edeceÄiniz gibi okurken sürekli ileri geri kodları inceliyorsunuz. Bu kullanıÅsız bir yöntem oldu, hele ki kod okumayla pek uÄraÅmayanlar nereye zıplayacaklarını anlayamayacaklardır.
Ayrıca step* fonksiyonu tek kullanımlık oldu. Amaç sadece "kıyamet piramidi"nden korunmak. Bu fonksiyonları baÅka kimse kullanmayacaktır. Böylece boÅ bir sürü isim kullandık ve çöplüÄe çevirdik.
Bu problemi çözmek için daha iyi bir yöntem mevcut.
Bunun için kullanılacak en iyi yöntemlerden biri âpromisesâ kullanmaktır. Bir sonraki bölümde bu konuya deÄineceÄiz.
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)