Bir fonksiyon hemen çalıÅtırılmak istenmeyebilir, belirli bir zaman sonra çalıÅması istenebilir. Buna âçaÄrıyı zamanlamaâ denir.
Bunun için iki metod var:
setTimeoutfonksiyonu belirli bir zaman sonra çalıÅtırmaya yarar.setIntervalfonksiyonun belirli aralıklar ile sürekli çalıÅmasını saÄlar.
Bu metodlar JavaScriptâin tanımları arasında yer almaz. Fakat çoÄu ortam bu metodları sunar. Daha özele inecek olursak tüm tarayıcılar ve NodeJS bu metodları saÄlar.
setTimeout
Yazımı:
let zamanlayiciId = setTimeout(fonk|kod, bekleme[, arg1, arg2...])
Parametreler:
fonk|kod- Fonksiyon veya çalıÅtırılacak kodun karakter dizisi hali. Genelde bu fonksiyon olur. Uyumluluk dolayısıyla karakter dizisi de gönderilebilir fakat önerilmez.
bekleme- Milisaniye cinsiden çalıÅmadan önceki bekleme süresi.(1000 ms = 1 saniye).
arg1,arg2â¦- Fonksiyon için gerekli argümanlar.( IE9 öncesinde çalıÅmaz.)
ÃrneÄin aÅaÄıdaki kod selamVer() fonksiyonunu bir saniye sonra çalıÅtırır:
function selamVer() {
alert('Selam');
}
setTimeout(selamVer, 1000);
Argümanlı versiyonu:
function selamVer(ifade, kim) {
alert( ifade + ', ' + kim );
}
setTimeout(selamVer, 1000, "Merhaba", "Ahmet"); // Merhaba, Ahmet
EÄer ilk argüman karakter dizisi ise, sonrasında JavaScript bundan fonksiyon üretir.
AÅaÄıdaki de aynı Åekilde çalıÅacaktır:
setTimeout("selamVer('Merhaba')", 1000);
Karakter dizisi olarak fonksiyon göndermek aslında pek önerilmez, bunun yerine aÅaÄıdaki gibi fonksiyon kullanılması daha doÄrudur:
setTimeout(() => alert('Merhaba'), 1000);
Yeni baÅlayan arkadaÅlar bazen yanlıÅlıkla fonksiyonun sonuna () ekleyebilir:
// yanlıÅ!
setTimeout(selamVer(), 1000);
Bu çalıÅmaz, çünkü setTimeout referans bir fonksiyon beklemektedir. Burada selamVer() derseniz fonksiyonu çalıÅtırırsınız ve bunun sonucu setTimeout fonksiyonu tarafından kullanılır. Bizim durumumuzda selamVer() undefined döndürür. ( fonksiyon ile alakalı bir sorun yok ) bundan dolayı hiçbir Åey zamanlanmaz.
clearTimeout fonksiyonu ile iptal etme
setTimeout çaÄrısı âtimer identifierâ döner. Bu timerId ile zamanlayıcıyı iptal edebiliriz.
Yazımı aÅaÄıdaki gibidir:
let timerId = setTimeout(...);
clearTimeout(timerId);
AÅaÄıdaki kodda önce bir zamanlayıcı test eder sonrasında ise bunu iptal eder. Sonuç olarak hiçbir Åey olmaz:
let timerId = setTimeout(() => alert("Bir Åey olmayacak"), 1000);
alert(timerId); // timer identifier
clearTimeout(timerId);
alert(timerId); // same identifier (iptal ettikten sonra null olmaz)
alert çıktısından da göreceÄiniz gibi timer bir id numarası ile tanımlanır. DiÄer ortamlarda bu baÅka bir Åey olabilir. ÃrneÄin Node.Js bir sayı yerine farklı metodları olan timer objesi döner.
Tekrar söylemek gerekirse üzerinde anlaÅılmıŠbir Åartname bulunmamaktadır.
Tarayıcılar için zamanlayıcılar zamanlayıcı bölümünde belirtilmiÅtir.
setInterval
setInterval setTimeout ile aynı yazıma sahiptir:
let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
Tüm argümanlar aynı anlama gelir. Fakat setTimeoutâa nazaran fonksiyonu sadece bir defa deÄil belirtilen zamanda sürekli olarak çalıÅtırır.
Bu zamanlayıcıyı iptal etmek için clearInterval(timerId) kullanılmalıdır.
AÅaÄıdaki örnekte mesaj her iki saniyede bir gönderilecektir. 5 saniye sonunda ise durdurulur.
// her iki sn'de tekrar et
let timerId = setInterval(() => alert('tick'), 2000);
// 5 saniye sonunda durdur.
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
IE ve Firefox tarayıcılarda ekranda alert/confirm/prompt olduÄu sürece zamanlayıcı çalıÅmaya devam eder, fakat Chrome, Opera ve Safari bu zamanı durdurur.
Bundan dolayı eÄer yukarıdaki kodu çalıÅtırır ve iptalâe basmazsanız Firefox/IEâde bir sonraki alert durmadan gösterilir. Fakat Chrome/Opera/Safariâde kapatıldıktan sonra 2 sn sonra tekrar alert gelir.
Tekrarlı setTimeout
Bir kodu düzenli olarak çalıÅtırmanın iki yolu bulunmaktadır.
İlki setInterval diÄeri ise aÅaÄıdaki gibi kullanılan setTimeout:
/** instead of:
let timerId = setInterval(() => alert('tick'), 2000);
*/
let timerId = setTimeout(function tick() {
alert('tick');
timerId = setTimeout(tick, 2000); // (*)
}, 2000);
setTimeout bir sonraki çaÄrıyı o anki çaÄrı bittiÄi ana planlar (*)
Kendini tekrar eden setInterval setTimeoutâdan daha esnektir. Bu Åekliyle kullanıldıÄında bir sonraki planlanan çaÄrı ana çaÄrının durumuna göre ötelebilir veya daha geriye alınabilir.
ÃrneÄin, her 5 snâde bir sunucudan veri isteyen bir servis yazmamız gerekmektedir. Fakat sunucuya fazladan yük binerse bunun 10,20,40 sn olarak deÄiÅtirilmesi gerekmektedir.
Bahsedilen kod aÅaÄıdaki gibidir:
let delay = 5000;
let timerId = setTimeout(function request() {
...talep gönder...
if (sunucu yüklenmesinden dolayı eÄer talep iptal olursa) {
// bir sonraki talep için gerekli süreyi uzat.
delay *= 2;
}
timerId = setTimeout(request, delay);
}, delay);
EÄer CPU-aç görevleriniz varsa bu görevlerin süresini ölçüp buna göre bir çalıÅma planı oluÅturmak mümkündür.
Kendini tekrar eden setInterval iki çaÄrı arasındaki süreyi garanti eder fakat setTimeout bunu garanti etmez.
AÅaÄıdaki iki kod parçacıÄı karÅılaÅtırılacak olursa:
let i = 1;
setInterval(function() {
func(i);
}, 100);
İkincisi tekrarlı setTimeout kullanmaktadır.
let i = 1;
setTimeout(function run() {
func(i);
setTimeout(run, 100);
}, 100);
setInterval func(i) fonksiyonunu her 100msâde bir çalıÅtırır.
Dikkatinizi çekti mi?â¦
func çaÄrıları arasındaki geçen süre koddan daha kısa.
DoÄal olan bu aslında çünkü func çalıÅtıÄında bu arlıÄın bir kısmını harcar.
Hatta bu func çalıÅmasının bizim beklediÄimiz 100msâden fazla olması da mümkündür.
Bu durumda JS Motoru func fonksiyonunun bitmesini bekler, sonra planlayıcıyı kontrol eder eÄer zaman geçmiÅse hiç beklemeden tekrar çalıÅtırır.
Bu durumda ile karÅılaÅıldıÄında fonksiyon hiç beklemeden sürekli çalıÅır.
AÅaÄıda ise kendini çaÄıran setTimeout gösterilmiÅtir:
Kendini çaÄıran setTimeout arada geçen sürenin aynı olmasını garanti eder.(burada 100ms).
Bunun nedeni yeni çaÄrının önceki çaÄrının bitiminde hesaplanmasından dolayıdır.
Bir fonksiyon setInterval/setTimeoutâa gönderildiÄinde içeride bir referansını oluÅturup zamanlayıcıya kaydeder. Bundan dolayı bu fonksiyon Ãöp toplama iÅlemine girmez. DıÅarıda hiçbir referans olmasa bile bu fonksiyon yok olmaz.
// zamanlayıcı çaÄırana kadar fonksiyon hafızada kalır.
setTimeout(function() {...}, 100);
setInterval metodu için fonksiyon cancelInterval çaÄırılmadıÄı sürece hafızada kalır.
Bunun yan etkisi ise, dıÅarıdaki fonksiyondan veri almak isteyen bir fonksiyon sürekli çaÄırılır ve ayakta kalırsa dıÅarıdaki deÄiÅkenlerin de sürekliliÄi devam eder. Asıl bu fonksiyonun kendisinden bile fazla hafıza kaplayabilir. Ãyleyse zamanlayıcı ile iÅiniz bittiÄinde en iyisi iptal etmektir. Bu fonksiyonunuz küçük olsa bile yapılması gereken bir iÅlemdir.
setTimeout(â¦,0)
setTimeOutâun farklı bir kullanım Åekli daha bulunmakta: setTimeout(func, 0)
Bu funcâın mümkün olduÄu anda zamanlanmasını saÄlar. Fakat zamanlayıcı bunu sadece o anki kod iÅlemi bittiÄinde gerçekleÅtirir.
Bundan dolayı zamanlayıcı o anki iÅin âhemen arkasındanâ çalıÅmaya baÅlar. DiÄer bir deyiÅle âasenkronâ.
ÃrneÄin aÅaÄıdaki kod önce âMerhabaâ ve hemen arkasından âDünyaâ yazdırır.
setTimeout(() => alert("Dünya"), 0);
alert("Merhaba");
İlk satırda âçaÄrıyı 0ms sonra sıraya koyâ demektir. Fakat zamanlayıcı bunu âönce sırayı kontrol etâ'ten sonra bakar yani o anki kodu çalıÅtırdıktan sonra. Bundan dolayı "Merhaba" önce yazılır "Dünya" sonra.
CPU-aç görevlerin parçalanması
setTimeout ile CPU-aç görevlerin kullanılabilmesi Åöyle bir yöntem kullanılabilir.
ÃrneÄin, yazıların renklerini deÄiÅtiren ( Åu anki sayfa gibi ) bir uygulama tam olarak CPU-aç bir uygulamadır. Analiz eder, birçok renkli eleman yaratır, bunları dökümana ekler dosya büyüdükçe bu da gittikçe daha fazla iÅlemci gerektirir. Hatta tarayıcının "hang " durumuna yani tepki vermemesine kadar gidebilir, bu da kabul edilemez.
Bundan dolayı uzun metinleri ayırabiliriz. Ãnce 100 satır, sonra diÄer bir 100 satır vs.
Daha basit bir örnekten anlatmaya çalıÅırsak. Bir fonksiyonunuz olsun ve 1âden 100000000000âa kadar saysın
EÄer kodu çalıÅtırırsanız iÅlemci tepki vermemeye baÅlar. Sunucu tabanlı JS kodlarında bu kolay bir Åekilde fark edilebilir fakat eÄer bu kodu tarayıcı üzerinde çalıÅtırıyorsanız diÄer butonlara tıkladıÄınızda JavaScriptâin durduÄunu ve bunun bitene kadar da baÅka bir Åeyin çalıÅmadıÄını görürsünüz.
let i = 0;
let start = Date.now();
function count() {
// yoÄun bir iÅ
for(let j = 0; j < 1e9; j++) {
i++;
}
alert((Date.now() - start) + 'ms de tamamlandı');
}
count();
Hatta taryıcı âbu kodun çalıÅması uzun zaman alıyorâ uyarısı verebilir.
Kodu setTimeout ile bölecek olursak:
let i = 0;
let start = Date.now();
function count() {
// zorlu görevin bir bölümünü yap (*)
do {
i++;
} while (i % 1e6 != 0);
if (i == 1e9) {
alert((Date.now() - start) + 'ms de tamamlandı');
} else {
setTimeout(count, 0); // yeni çaÄrıyı zamanla (**)
}
}
count();
Åimdi tarayıcı ekranı âsayma iÅlemiâ gerçekleÅirken tamamen çalıÅır durumdadır.
İÅin bir bölümü Åu Åekilde yapılır (*)
- İlk çalıÅma:
i=1...1000000. - ikinci çalıÅma:
i=1000001..2000000 - ⦠bu Åekilde while
inin100000âe bölünüp bölünmediÄine kadar.
EÄer iÅlem hala bitmemiÅse (**) zamanlayıcısı tekrar çalıÅır.
Sayaç çalıÅırken duraklama yapılması JavaScript motoruna ânefes almasıâ ve baÅka iÅ yapabilmesi için zaman saÄlar.
Dikkat edilmesi gereken nokta: setInterval kullanılarak ve kullanılmadan yapılan iki testin çalıÅma süreleri çok farklı deÄildir.
Bu süreleri daha da yakınlaÅtırabilmek için neler yapılabilir bakalım.
Zamanlamayı count() fonksiyonunun baÅına alalım:
let i = 0;
let start = Date.now();
function count() {
// zamanlama baÅa taÅındı
if (i < 1e9 - 1e6) {
setTimeout(count, 0); // yeni çaÄrıyı zamanla
}
do {
i++;
} while (i % 1e6 != 0);
if (i == 1e9) {
alert((Date.now() - start) + 'ms de tamamlandı');
}
}
count();
Åimdi ise count() ile baÅlıyoruz ve count fonksiyonunun birden fazla çaÄırılacaÄınız biliyoruz.
ÃalıÅtırırsanız belirgin biçimde daha kısa süreceÄini göreceksiniz.
Tarayıcıda, iç içe zamanlayıcıların kullanımına ait bir limit bulunmaktadır. HTML5 standard dediÄine göre: â5 iç içe zamanlayıcıdan sonra, döngü en az 4 ms durmak zorundadır.â
Bunu aÅaÄıdaki bulunan örnekte gösterelim. setTimeout çaÄrısı kendisini 0ms sonra tekrarn çaÄırıyor. Her bir çaÄrı bir öncekinin zamanını times dizisinden hatırlıyor. Gecikme nasıl olacak bakalım:
let start = Date.now();
let times = [];
setTimeout(function run() {
times.push(Date.now() - start); // bir önceki çaÄrıdaki gecikmeyi hatırla.
if (start + 100 < Date.now()) alert(times); // gecikme 100ms den büyükse göster
else setTimeout(run, 0); // deÄilse tekrar zamanla
}, 0);
// Ãıktının örneÄi:
// 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100
İlk zamanlayıcılar anında çalıÅacaktır ( dökümantasyonda yazdıÄı gibi ) bundan dosnra gecikmeler oyuna dahil olur. 9, 15, 20, 24...
Bu limitasyonların nedeni de yine eski zamanlara dayanmaktadır. ÃoÄu kod bu prensibe göre çalıÅtıÄından dolayı bu kurallar devam etmektedir.
Sunucu tabanlı JavaScript için ise bu kısıtlama geçerli deÄildir. Ayrıca anlık olarak asenkronron iÅlerin zamanlaması amacıyla baÅka yollar da bulunmaktadır. ÃrneÄin process.nextTick ve setImmediate gibi. Yani buradaki kısıtlamanın tarayıcı bazlı olduÄu sonucunu çıkarabilirsiniz.
Tarayıcının iÅlemesine izin vermek.
Tarayıcı taraflı kodların diÄer bir yararı ise kullanıcıya progresss bar(ilerleme çubuÄu) tarzında görselleri sunabilmesidir. Tarayıcı genelde âtekrar boyamaâ kod tekrarlandıktan sonra ( repainting) iÅlemi yaptıÄından.
Bundan dolayı diyelim ki çok büyük iÅler yapan bir fonksiyon olsa bile, dökümanda bulunan deÄiÅiklik bu iÅlem tamamlandıktan sonra gerçekleÅir.
ÃrneÄiN:
<div id="progress"></div>
<script>
let i = 0;
function count() {
for(let j = 0; j < 1e6; j++) {
i++;
// anlık i deÄerini dive yazdır. <div>
// ( innerHTML ile ilgili daha sonraki bölümlerde konuÅulacaktır.)
progress.innerHTML = i;
}
}
count();
</script>
Bunu çalıÅtıdıÄınızda iânin deÄiÅikliÄi tüm sayma iÅlemi bittikten sonra görünür hale gelir.
eÄer bunu setTimeout ile parçalara bölecek olursak bu defa daha güzel bir Åekilde görünecektir.
<div id="progress"></div>
<script>
let i = 0;
function count() {
// yoÄun iÅin bir bölümü (*)
do {
i++;
progress.innerHTML = i;
} while (i % 1e3 != 0);
if (i < 1e9) {
setTimeout(count, 0);
}
}
count();
</script>
Artık <div> i nin yükselen deÄerini gösterecektir.
Ãzet
setInterval(func, delay, ...args)vesetTimeout(func, delay, ...args)metodlarıfuncâın düzenli olarakdelayms aralıklar ile çalıÅmasını saÄlar.- ÃalıÅmayı durdurmak için
clearInterval/clearTimoutfonksiyonlarısetInterval/setTimeoutmetodundan dönen deÄerler ile çaÄırılmalıdır. - İç içe
setTimeoutçaÄrısı kullanmaksetIntervalâe göre daha esnektir. Ayrıca bu Åekilde aralarda en kısa süre beklemesini saÄlar. - 0 gecikmeli zamanlayıcı ise
setTimeout(...,0)zamanlayıcıyı olabildiÄince çabuk fakat o anki koddan sonra çaÄırılacak Åekilde zamanlar.
setTimeout(...,0)'ın bazı kullanım durumları:
- CPU-aç görevleri parçalara ayırmak için, böylece kod sürekli tepki verebilir.
- Böylece görev devam ederken tarayıcının baÅka iÅlere ( ilerleme çubuÄu ) zaman ayırır.
Tüm zamanlama metodları tam olarak gecikmeyi garantilemez. Zamanlayıcıda bu varsayımın üzerine bir Åey inÅa etmeyin.
ÃrneÄin, tarayıcı zamanı birçok nedenden ötürü yavaÅlayabilir:
- İÅlemcinin yükü artarsa.
- Tarayıcının tabâı arka plana alındıysa.
- Laptop batarya ile çalıÅıyorsa.
Bunların hepsi tarayıcı zamanına etki eder. Aralardaki gecikme 300ms ile 1000ms arasında deÄiÅebilir. Tabi tarayıcı ve özellikleri de bu konuda etkin rol oynar.
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)