Biz funktsiyani hozir emas, balki malum bir vaqtdan keyin bajarishga qaror qilishimiz mumkin. Bu chaqiruvni rejalashtirish deb nomlanadi.
Buning ikkita usuli mavjud:
setTimeoutvaqt oraligidan keyin bir marta funktsiyani bajarishga imkon beradi.setIntervalfunktsiyalarni muntazam ravishda ishlash oraligida ishlashga imkon beradi.
Ushbu usullar JavaScript spetsifikatsiyasining bir qismi emas. Ammo aksariyat muhitlar ichki rejalashtiruvchiga ega va ushbu usullarni taqdim etadi. Xususan, ular barcha brauzerlarda va Node.js qollab-quvvatlanadi.
setTimeout
Sintaksis:
let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)
Parametrlar:
func|code- Funksiya yoki bajariladigan kod matni. Odatda, bu funktsiya. Tarixiy sabablarga kora kod matnni otkazish mumkin, ammo bu tavsiya etilmaydi.
delay- Ishlashdan oldin kechikish, millisekundlarda (1000 ms = 1 soniya), sukut boyicha 0.
arg1,arg2- Funktsiya uchun argumentlar (IE9- da qollab-quvvatlanmaydi)
Masalan, ushbu kod bir soniyadan song sayHi() ni chaqiradi:
function sayHi() {
alert('Salom');
}
setTimeout(sayHi, 1000);
Argumentlar bilan:
function sayHi(phrase, who) {
alert( phrase + ', ' + who );
}
setTimeout(sayHi, 1000, "Salom", "John"); // Salom, John
Agar birinchi argument matn bolsa, unda JavaScript undan funktsiya yaratadi.
Shunday qilib, bu ham ishlaydi:
setTimeout("alert('Salom')", 1000);
Ammo matnlardan foydalanish tavsiya etilmaydi, ularning orniga funktsiyalardan foydalaning, masalan:
setTimeout(() => alert('Salom'), 1000);
Ajam dasturchilar bazida funktsiyadan keyin () qavslarini qoshib xato qilishadi:
// noto'g'ri!
setTimeout(sayHi(), 1000);
Bu ishlamaydi, chunki setTimeout funktsiyaga havolani kutadi. Va bu yerda sayHi() funktsiyasi ishlaydi va bajarilish natijasi setTimeout ga uzatiladi. Bizning holatimizda sayHi() natijasi undefined (funktsiya hech narsa bermaydi), shuning uchun hech narsa rejalashtirilmagan.
clearTimeout yordamida bekor qilish
setTimeout ga chaqiruv bajarishni bekor qilish uchun ishlatadigan taymer identifikatori timerId ni qaytaradi.
Bekor qilish uchun sintaksis:
let timerId = setTimeout(...);
clearTimeout(timerId);
Quyidagi kodda biz funktsiyani rejalashtiramiz va keyin uni bekor qilamiz (fikrimizni ozgartirdik). Natijada, hech narsa bolmaydi:
let timerId = setTimeout(() => alert("never happens"), 1000);
alert(timerId); // taymer identifikatori
clearTimeout(timerId);
alert(timerId); // bir xil identifikator (bekor qilinganidan keyin null bo'lmaydi)
alert natijasidan korinib turibdiki, brauzerda taymer identifikatori raqam hisoblanadi. Boshqa muhitda bu boshqa narsa bolishi mumkin. Masalan, Node.js taymer moslamasini qoshimcha usullar bilan qaytaradi.
Shunga qaramay, ushbu usullar uchun universal spetsifikatsiya mavjud emas, shuning uchun hammasi yaxshi.
Brauzerlar uchun taymerlar HTML5 standartidagi taymerlar bolimida tasvirlangan.
setInterval
setInterval usuli setTimeout bilan bir xil sintaksisga ega:
let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
Barcha argumentlar bir xil manoga ega. setTimeout dan farqli olaroq, u funktsiyani faqat bir marta emas, balki berilgan vaqt oraligidan keyin muntazam ravishda ishlaydi.
Boshqa chaqiruvlarni toxtatish uchun biz clearInterval(timerId) ni chaqirishimiz kerak.
Quyidagi misol xabarni har 2 soniyada korsatadi. 5 soniyadan song chiqish toxtatiladi:
// 2 soniya oralig'ida takrorlang
let timerId = setInterval(() => alert('tick'), 2000);
// 5 soniyadan keyin to'xtaydi
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
IE va Firefox brauzerlarida ichki taymer alert/confirm/prompt korsatishda yurishni ni davom ettiradi, ammo Chrome, Opera va Safari-da ichki taymer muzlatilgan boladi.
Shunday qilib, agar siz yuqoridagi kodni ishlatsangiz va alert oynasini bir muncha vaqt ochirmasangiz, unda Firefox/IE-da keyingi alert darhol bajariladi (oldingi chaqiruvdan 2 soniya song) va Chrome/Opera/Safari yana 2 soniyadan song (taymer alert vaqtida toxtaydi).
Rekursiv setTimeout
Biror narsani muntazam ravishda bajarishning ikkita usuli mavjud.
Ulardan biri setInterval. Ikkinchisi quyidagicha rekursiv setTimeout:
/** ning o'rniga:
let timerId = setInterval(() => alert('tick'), 2000);
*/
let timerId = setTimeout(function tick() {
alert('tick');
timerId = setTimeout(tick, 2000); // (*)
}, 2000);
Yuqoridagi setTimeout navbatdagi chaqiruvni joriy chaqiruv oxirida (*) rejalashtiradi.
Rekursiv setTimeout setInterval ga qaraganda ancha moslashuvchan usul. Shu tarzda, keyingi chaqiruv joriy chaqiruv natijalariga qarab boshqacha tarzda rejalashtirilishi mumkin.
Masalan, biz serverga har 5 soniyada malumot sorab sorov yuboradigan xizmatni yozishimiz kerak, ammo server haddan tashqari yuklangan bolsa, u intervalni 10, 20, 40 soniyagacha oshirishi kerak
Mana, psevdokod:
let delay = 5000;
let timerId = setTimeout(function request() {
...so'rov yuboring...
if (server haddan tashqari yuklanganligi sababli so'rov bajarilmadi) {
// keyingi ishga tushirish oralig'ini oshiring
delay *= 2;
}
timerId = setTimeout(request, delay);
}, delay);
Agar bizda doimiy ravishda protsessorga chalingan vazifalar bolsa, unda biz bajarilish vaqtini olchay olamiz va ertami-kechmi navbatdagi chaqiruvni rejalashtiramiz.
Rekursiv setTimeout qatllar orasidagi kechikishni kafolatlaydi, setInterval esa bunday qilmaydi.
Keling, ikkita kod qismini taqqoslaylik. Birinchisi setInterval dan foydalanadi:
let i = 1;
setInterval(function() {
func(i++);
}, 100);
Ikkinchisi rekursiv setTimeout dan foydalanadi:
let i = 1;
setTimeout(function run() {
func(i++);
setTimeout(run, 100);
}, 100);
setInterval uchun ichki rejalashtiruvchi har 100 msda func(i) ishlatadi:
Siz sezdingizmi?
setInterval uchun func chaqiruvlari orasidagi haqiqiy kechikish koddan kamroq!
Bu odatiy holdir, chunki func bajarilishi vaqti oraliqning bir qismini sarflaydi.
Ehtimol, func ning bajarilishi biz kutganimizdan uzoqroq bolishi va 100ms dan oshishi mumkin.
Bunday holda, interpretator func tugashini kutadi, songra rejalashtiruvchini tekshiradi va agar vaqt tugagan bolsa, uni darhol qayta ishlatadi.
Chekka holatda, agar funktsiya har doim kechikish ms dan koproq vaqtni bajaradigan bolsa, u holda chaqiruvlar umuman pauza qilinmasdan amalga oshiriladi.
Va bu yerda setTimeout rekursivi uchun rasm:
Rekursiv setTimeout belgilangan kechikishni kafolatlaydi (bu yerda 100ms).
Buning sababi, avvalgi chaqiruv oxirida yangi chaqiruv rejalashtirilgan.
Funktsiya setInterval/setTimeout ga otkazilganda, unga ichki havolalar yaratiladi va rejalashtiruvchida saqlanadi. Bu funktsiyani axlat yigishdan saqlaydi, hatto unga boshqa havolalar bolmasa ham.
// rejalashtiruvchi chaqirguncha funktsiya xotirada qoladi
setTimeout(function() {...}, 100);
setInterval uchun funktsiya clearInterval chaqirilguncha xotirada qoladi.
Yon tasiri bor. Funktsiya tashqi leksik muhitga murojaat qiladi, shuning uchun u yashab turib, tashqi ozgaruvchanlar ham yashaydi. Ular funktsiyadan kora koproq xotirani olishlari mumkin. Shunday qilib, biz endi rejalashtirilgan funktsiyaga muhtoj bolmaganda, juda kichik bolsa ham, uni bekor qilish yaxshiroqdir.
Zero delay setTimeout
Maxsus foydalanish holati mavjud: setTimeout(func, 0) yoki shunchaki setTimeout(func).
Bu func ning bajarilishini eng qisqa vaqt ichida rejalashtiradi. Ammo rejalashtiruvchi uni joriy kod tugagandan keyingina chaqiradi.
Shunday qilib, funktsiya joriy kodi darhol ishlashni rejalashtirmoqda. Boshqacha qilib aytganda, asenkron ravishda.
Masalan, Salom, keyin darhol Dunyo chiqadi:
setTimeout(() => alert("Dunyo"));
alert("Salom");
Birinchi satr chaqiruvni 0ms dan keyin kalendarga kiritadi. Ammo rejalashtiruvchi joriy kod tugagandan keyingina kalendari tekshiradi, shuning uchun birinchi bolib "Salom", keyin esa "Dunyo" boladi.
CPU vazifalarni ajratish
setTimeout dan foydalanib, CPU och vazifalarni ajratish uchun hiyla-nayrang bor.
Masalan, sintaksisni takidlaydigan skript (ushbu sahifadagi kod misollarini ranglash uchun ishlatiladi) juda ogir protsessorga ega. Kodni ajratib korsatish uchun u tahlilni amalga oshiradi, koplab rangli elementlarni yaratadi, ularni hujjatga qoshadi kop narsalarni talab qiladigan katta matn uchun. Hatto brauzer osilib qolishi mumkin, bu esa juda yomon.
Shunday qilib, biz uzun matnni qismlarga ajratishimiz mumkin. Dastlab 100 satr, songra setTimeout(..., 0) va boshqalarni ishlatib yana 100 satrni rejalashtiring.
Aniqlik uchun korib chiqish uchun oddiyroq misol keltiramiz. Bizda 1 dan 1000000000 gacha hisoblash funktsiyasi mavjud.
Agar siz uni ishlatsangiz, CPU osilib qoladi. Server tomonidagi JS uchun bu aniq seziladi va agar siz uni brauzerda ishlatayotgan bolsangiz, sahifadagi boshqa tugmachalarni bosishga harakat qilsangiz aslida JavaScript toxtatib qoyganligini korasiz, u tugamaguncha boshqa harakatlar ishlamaydi.
let i = 0;
let start = Date.now();
function count() {
// og'ir ish qilish
for (let j = 0; j < 1e9; j++) {
i++;
}
alert("Done in " + (Date.now() - start) + 'ms');
}
count();
Brauzerda hatto skript juda uzoq davom etadi degan ogohlantirish korsatilishi mumkin (lekin umid qilamanki bunday bolmaydi, chunki ularning soni unchalik katta emas).
Ichki ornatilgan setTimeout yordamida ishni ikkiga bolaylik:
let i = 0;
let start = Date.now();
function count() {
// og'ir ishning bir qismini bajaring (*)
do {
i++;
} while (i % 1e6 != 0);
if (i == 1e9) {
alert("Done in " + (Date.now() - start) + 'ms');
} else {
setTimeout(count); // yangi chaqiruvni rejalashtirish (**)
}
}
count();
Endi brauzer interfeysi hisoblash jarayonida toliq ishlaydi.
Biz ishning bir qismini bajaramiz (*):
- Birinchi bajarilish:
i=1...1000000. - Ikkinchi bajarilish:
i=1000001..2000000. - va shnday,
while,ining1000000ga teng bolinishini tekshiradi.
Keyinchalik, agar biz hali tugamagan bolsak, keyingi chaqiruv (**) da rejalashtirilgan.
count ijrolari orasidagi pauzalar JavaScript-ni interpretatoriga boshqa biron bir narsani qilishi va boshqa foydalanuvchi harakatlariga munosabat bildirishi uchun yetarli darajada nafas beradi.
Diqqatga sazovor tomoni shundaki, ikkala variant ham ishni setTimeout ga ajratish bilan va bolmasdan tezligi bilan taqqoslanadi. Umumiy hisoblash vaqtida katta farq yoq.
Ularni yaqinlashtirish uchun keling, ozgarish kiritamiz.
Biz rejalashtirishni count() boshiga otkazamiz:
let i = 0;
let start = Date.now();
function count() {
// rejalashtirishni boshiga siljiting
if (i < 1e9 - 1e6) {
setTimeout(count); // yangi chaqiruvni rejalashtirish
}
do {
i++;
} while (i % 1e6 != 0);
if (i == 1e9) {
alert("Done in " + (Date.now() - start) + 'ms');
}
}
count();
Endi biz count() ni boshlaganimizda va yana count() kerakligini bilsak, ishni darhol bajarishdan oldin rejalashtiramiz.
Agar siz uni ishlatsangiz, unda sezilarli darajada kam vaqt talab etilishini sezasiz.
Brauzerda ichki ornatilgan taymerlarning ishlash tezligi cheklangan. HTML5 standarti: beshta ichki taymerdan song, interval kamida tort millisoniyaga teng bolishga majbur boladi.
Keling, bu nimani anglatishini quyidagi misol bilan namoyish etamiz. Undagi setTimeout chaqiruvi 0ms dan keyin ozini qayta rejalashtiradi. Har bir chaqiruv oldingi vaqtdan haqiqiy vaqtni times satrida eslab qoladi. Haqiqiy kechikishlar qanday korinishga ega? Koraylikchi:
let start = Date.now();
let times = [];
setTimeout(function run() {
times.push(Date.now() - start); // oldingi chaqiruvdan kechikishni eslash
if (start + 100 < Date.now()) alert(times); // 100ms dan keyin kechikishlarni ko'rsating
else setTimeout(run); // else re-schedule
});
// chiqish namunasi:
// 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100
Birinchi taymerlar darhol ishlaydi (xuddi shu xususiyatda yozilganidek), keyin kechikish paydo boladi va biz 9, 15, 20, 24... koramiz.
Ushbu cheklov qadimgi davrlardan kelib chiqqan va koplab skriptlar unga tayanadi, shuning uchun u tarixiy sabablarga kora mavjud.
Server tomonidagi JavaScript-da bu cheklov mavjud emas va zudlik bilan mos kelmaydigan ishni rejalashtirishning boshqa usullari mavjud, masalan process.nextTick va setImmediate Node.js uchun. Shunday qilib, tushuncha faqat brauzerga xosdir.
Brauzerga korsatishga ruxsat berish
Brauzer ichidagi skriptlarning yana bir foydasi shundaki, ular foydalanuvchi uchun rivojlanish satrini yoki biror narsani korsatishi mumkin. Buning sababi shundaki, brauzer odatda skript tugagandan song barcha boyash ishlarini bajaradi.
Shunday qilib, agar biz bitta katta funktsiyani bajaradigan bolsak, u biror narsani ozgartirgan bolsa ham, ozgarishlar tugamaguncha hujjatda aks etmaydi.
Mana demo:
<div id="progress"></div>
<script>
let i = 0;
function count() {
for (let j = 0; j < 1e6; j++) {
i++;
// joriy i ni <div> ga qo'ying
// (biz bu yerda aniq bo'lishi kerak, ichki HTML haqida ko'proq maxsus bobda gaplashamiz)
progress.innerHTML = i;
}
}
count();
</script>
Agar siz uni ishlatsangiz, i ga ozgartirishlar butun hisoblash tugagandan song paydo boladi.
Agar biz uni qismlarga ajratish uchun setTimeout dan foydalansak, unda ozgarishlar oraligida qollaniladi, shuning uchun bu yaxshiroq korinadi:
<div id="progress"></div>
<script>
let i = 0;
function count() {
// og'ir ishning bir qismini bajaring (*)
do {
i++;
progress.innerHTML = i;
} while (i % 1e3 != 0);
if (i < 1e9) {
setTimeout(count);
}
}
count();
</script>
Endi <div> i ning ortib borayotgan qiymatlarini korsatadi.
Xulosa
setInterval(func, delay, ... args)vasetTimeout(func, delay, ... args)usullarifunckechikish milisoniyalardan keyin muntazam/bir marta bajarishga imkon beradi.- Amalga oshirishni bekor qilish uchun
setInterval/setTimeouttomonidan qaytarilgan qiymat bilanclearInterval/clearTimeoutni chaqirishimiz kerak. - Ichki
setTimeoutchaqiruvlarisetIntervalga moslashuvchan alternativa. Shuningdek, ular qatl etish oraligidagi minimal vaqtni kafolatlashlari mumkin. setTimeout(..., 0)nol-timeout rejalashtirish chaqiruvni iloji boricha tezroq, lekin joriy kod tugagandan song rejalashtirish uchun ishlatiladi.
Bazilar setTimeout(..., 0) holatlaridan foydalanadilar:
- Skript osilib qolmasligi uchun protsessorga och vazifalarni qismlarga bolish.
- Jarayon davom etayotganida brauzerga boshqa narsani qilishga ruxsat berish (bajarilish satrini boyash).
Iltimos, barcha rejalashtirish usullari aniq kechiktirishga kafolat bermasligini unutmang. Biz rejalashtirilgan kodda bunga ishonmasligimiz kerak.
Masalan, brauzer ichidagi taymer juda kop sabablarga kora sekinlashishi mumkin:
- CPU haddan tashqari yuklangan.
- Brauzer yorligi fon rejimida.
- Noutbuk batareyada.
Brauzer va sozlamalarga qarab, taymerning minimal taymer olchamlarini (minimal kechikish) 300 yoki hatto 1000 soniyaga oshirishi mumkin.
Izohlar
<code>yorlig'ini ishlating, bir nechta satrlar uchun - ularni<pre>yorlig'i bilan o'rab qo'ying, 10 satrdan ortiq bo'lsa - sandbox (plnkr, jsbin, codepen)