IlmHamroh
JavaScript Full-stack/11-qism. Asinxron JavaScript va tarmoq2/32-dars23 daqiqa
Mundarija (36)

JavaScript taymerlari: setTimeout va setInterval

Qisqacha: setTimeout(fn, ms) — fn ni bir marta, kamida ms millisekunddan keyin chaqiradi. setInterval(fn, ms) — har ms da qayta-qayta chaqiradi. Ikkalasi ham taymer identifikatorini qaytaradi: uni clearTimeout yoki clearInterval ga bersangiz, taymer bekor bo'ladi. Kechikish — kafolat emas, eng kam chegara: stek band bo'lsa, callback kutadi.

Bu darsda

  • setTimeout va setInterval bilan ishni kechiktirasiz va takrorlaysiz, callback'ga argument berasiz.
  • Taymerni clearTimeout/clearInterval bilan bekor qilasiz va brauzer bilan Node qaytaradigan taymer qiymati farqini bilasiz.
  • "0 ms" nimani anglatishini va nega taymer hech qachon aniq o'z vaqtida ishlamasligini tushuntira olasiz.
  • setInterval o'rniga rekursiv setTimeout qachon yaxshiroq ekanini o'lchab ko'rasiz.
  • vazifalar dagi e'lonni 3 soniyadan keyin o'zi yo'qoladigan qilasiz.

Oldin bilishingiz kerak: Sinxron va asinxron kod, Funksiya — qiymat: callback va HOF, Closure amaliyotda, this yo'qolishi va uni tiklash.

1. Nega bu kerak?

«Bahor» saytida vaqtga bog'liq ishlar ko'p:

  • Mehmon bron qildi — ekranda "Bron qabul qilindi" xabari chiqadi va 3 soniyadan keyin o'zi yo'qoladi.
  • Oshxona ekranida har buyurtma yonida taymer: "Manti — 12:40 dan beri". U har soniyada yangilanadi.
  • "Bronni bekor qilish" tugmasi faqat 5 soniya faol turadi, keyin yo'qoladi.

Uchalasida ham "keyinroq" yoki "har safar" bor. O'tgan darsda ko'rdik: kutish uchun bo'sh sikl yozib bo'lmaydi — sahifa qotadi. Kutishni brauzerga topshirish kerak. Buning eng oddiy vositasi — taymerlar.

setTimeout bilan Callback va HOF darsidan tanishsiz, clearTimeout ni esa 09-qismda (Fokus, scroll va resize) ko'rgansiz. 10-qismda Taymer klassi setInterval ga metod bergan edi (Class maydonlari). Bugun ularni oxirigacha ochamiz.

Taymer — oshxonadagi qum soati yoki telefon budilniki kabi. Siz uni qo'yasiz va ishingizni davom ettirasiz. Vaqt kelganda u chalinadi. Lekin qo'lingiz band bo'lsa, budilnik chalinsa ham javob berolmaysiz — ishni tugatib, keyin qaraysiz.

2. setTimeout: bir marta, keyinroq

2.1 Tuzilishi

Taymer (timer) — ma'lum vaqtdan keyin funksiyani chaqirishni host'ga topshirish.

js
function choyTayyor() {
  console.log("Ko'k choy damlandi!");
}

setTimeout(choyTayyor, 1500);
console.log("Choy damlashga qo'yildi");

Konsolda:

text
Choy damlashga qo'yildi
Ko'k choy damlandi!

setTimeout ikki asosiy argument oladi:

  1. Callback — keyinroq chaqiriladigan funksiya. Bu yerda choyTayyor — qavssiz, chunki funksiyaning o'zini beramiz, uni chaqirmaymiz.
  2. Kechikish (delay) — millisekundlarda. 1000 ms = 1 soniya, 1500 ms = 1,5 soniya. Berilmasa — 0.

Funksiya nomli bo'lishi shart emas. Ko'pincha arrow funksiya to'g'ridan-to'g'ri yoziladi: setTimeout(() => console.log("..."), 1500).

2.2 Callback'ga argument berish

Callback'ga qiymat kerak bo'lsa, ikki yo'l bor. Birinchisi — arrow funksiyaga o'rash:

js
function stolgaOlibBor(taom, stol) {
  console.log(`${taom} — ${stol}-stolga`);
}

setTimeout(() => stolgaOlibBor("Osh", 5), 300);

Konsolda:

text
Osh — 5-stolga

Ikkinchisi — kechikishdan keyingi argumentlar. setTimeout ularni callback'ga o'zi uzatadi:

js
function stolgaOlibBor(taom, stol) {
  console.log(`${taom} — ${stol}-stolga`);
}

setTimeout(stolgaOlibBor, 300, "Osh", 5);
setTimeout(stolgaOlibBor, 100, "Ko'k choy", 3);

Konsolda:

text
Ko'k choy — 3-stolga
Osh — 5-stolga

Ikkala yo'l ham to'g'ri. Arrow funksiya ko'proq uchraydi — o'qishda tushunarli. Ikkinchi taymer kodda keyin yozilgan, lekin birinchi ishladi: uning kechikishi qisqaroq.

2.3 "Kamida", "aynan" emas

O'tgan darsdagi tajribani eslang: 100 ms li taymer stek band bo'lgani uchun 1000 ms dan keyin ishladi. Bu — taymerning asosiy qoidasi:

Diqqat: Kechikish — callback'ning eng erta chaqirilish vaqti. Vaqt kelganda callback navbatga tushadi va stek bo'shashini kutadi. Shuning uchun taymer hech qachon erta ishlamaydi, lekin kech ishlashi mumkin.

Stek bo'sh bo'lsa ham bir necha millisekund kechikish odatiy: operatsion tizim taymerlarni ma'lum aniqlikda sanaydi. Windows'da bu farq 15 ms gacha yetadi. Shuning uchun darslarda o'lchangan vaqtni doim yaxlitlaymiz.

Tekshirib ko'ring: setTimeout(salomBer(), 1000) yozildi. Bir soniyadan keyin salom chiqadimi?

Javob

Yo'q — salom darhol chiqadi. salomBer() qavslar bilan — bu chaqiruv. JavaScript avval uni bajaradi va natijasini (undefined) setTimeout ga beradi. To'g'risi — qavssiz: setTimeout(salomBer, 1000). Bu xatoni Callback va HOF darsida ko'rgan edik — taymerlarda u eng ko'p uchraydi.

3. Taymerni bekor qilish

3.1 Taymer identifikatori va clearTimeout

setTimeout qaytaradigan qiymat — taymer identifikatori (timer ID): aynan shu taymerni ko'rsatadigan belgi. Uni saqlab qo'ysangiz, taymerni vaqti kelishidan oldin bekor qila olasiz.

Mehmon bron qildi. Bron 1 soniyadan keyin tasdiqlanadi, lekin u yarim soniyada fikridan qaytdi:

js
const bronTaymeri = setTimeout(() => {
  console.log("Bron tasdiqlandi: 5-stol, 19:00");
}, 1000);

console.log("Bron 1 soniyadan keyin tasdiqlanadi...");

setTimeout(() => {
  clearTimeout(bronTaymeri);
  console.log("Mehmon bronni bekor qildi");
}, 500);

Konsolda:

text
Bron 1 soniyadan keyin tasdiqlanadi...
Mehmon bronni bekor qildi

"Bron tasdiqlandi" hech qachon chiqmadi. clearTimeout(bronTaymeri) taymerni host'dan olib tashladi — callback navbatga ham tushmadi.

Gmail'dagi "Undo" ("Bekor qilish") tugmasi aynan shunday ishlaydi: xat bir necha soniya "kutish" taymerida turadi va siz bossangiz — clearTimeout.

3.2 Bekor qilib bo'lmaydigan holatlar xato bermaydi

clearTimeout ni xavfsiz chaqirish mumkin. Taymer allaqachon ishlab bo'lgan bo'lsa yoki identifikator umuman yo'q bo'lsa — hech narsa bo'lmaydi:

js
clearTimeout(undefined);
clearTimeout(null);
clearTimeout(12345);
console.log("Xato yo'q"); // Xato yo'q

Bu qulay: "taymer bormi?" deb tekshirmasdan, har safar clearTimeout ni chaqirib qo'yish mumkin. «Vazifalar qadami» da aynan shunday qilamiz.

3.3 Brauzer va Node: identifikator har xil

Identifikatorning o'zi hostga bog'liq. Brauzerda u — musbat butun son:

js
const taymer = setTimeout(() => {}, 1000);
console.log(typeof taymer); // number
console.log(taymer); // masalan, 3

Node'da esa — Timeout obyekti:

js
const taymer = setTimeout(() => {}, 1000);
console.log(typeof taymer); // object
console.log(taymer.constructor.name); // Timeout
clearTimeout(taymer);

Ikkala holatda ham bu qiymatni faqat saqlab, keyin clearTimeout ga berasiz — ichidagi nima ekani muhim emas. Shuning uchun bir xil kod ikkala hostda ham ishlaydi. Bu ikki blok ostida «Ishga tushir» tugmasi yo'q. Birinchisini brauzerning DevTools konsolida sinang (F12 → Console), ikkinchisini — kompyuteringizda node bilan. Pastdagi sekundomer ham brauzerdagi identifikatorni konsolga chiqaradi.

Node'ning Timeout obyektida qo'shimcha metodlar bor. Masalan, taymer.unref() — "faqat shu taymer qolgan bo'lsa, dasturni tugatishga xalaqit berma". Node haqida 24-qismda gaplashamiz — hozir bilish shart emas.

Tekshirib ko'ring: Nega clearTimeout(bronTaymeri) ni bronTaymeri taymeri ishlab bo'lgandan keyin chaqirsa ham xato chiqmaydi?

Javob

Taymer ishlab bo'lgach, host uni o'z ro'yxatidan o'chiradi. clearTimeout ro'yxatdan shu identifikatorni qidiradi va topmasa — jim qaytadi. Bu standartda shunday belgilangan, aynan xavfsiz chaqirish uchun.

4. setInterval: har safar

4.1 Takrorlash va to'xtatish

setInterval(fn, ms) — fn ni har ms millisekundda, to'xtatilguncha qayta-qayta chaqiradi. To'xtatish — clearInterval(identifikator).

Rustam aka oshxona ekranida qaytuvchi sanoqni ko'rmoqchi:

js
let qoldi = 3;

const sanoq = setInterval(() => {
  console.log(`${qoldi}...`);
  qoldi--;
  if (qoldi === 0) {
    clearInterval(sanoq);
    console.log("Osh tayyor!");
  }
}, 300);

Konsolda:

text
3...
2...
1...
Osh tayyor!

Uch narsaga qarang:

  • Birinchi chaqiruv darhol emas, 300 ms dan keyin bo'ldi. setInterval boshlanishida ham kutadi.
  • qoldi — callback'dan tashqarida. Callback har safar yangi kontekstda ishlaydi (Execution context), lekin closure orqali o'sha bitta qoldi ni ko'radi (Closure).
  • clearInterval(sanoq) — callback'ning ichida. To'xtatish sharti bajarilganda interval o'zini o'zi to'xtatdi.

Qadamma-qadam qarang. O'ngda — host'dagi taymer (Web API) va navbat:

Oxirgi qadamga e'tibor bering: Node dasturni faqat host'da faol taymer qolmaganda tugatadi. clearInterval unutilsa, dastur hech qachon tugamaydi — terminalda Ctrl+C bilan to'xtatishga to'g'ri keladi.

4.2 Jonli misol: sekundomer

Brauzerda setInterval ko'pincha ekranni yangilash uchun ishlatiladi. Quyidagi sekundomerda tugmalarni sinab ko'ring — «Boshlash» ni ketma-ket ikki marta ham bosing:

html
<style>
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
  .soat { font: 700 2.5rem/1.2 ui-monospace, monospace; }
  button { font: inherit; padding: 0.4rem 0.9rem; }
</style>
<p class="soat" id="soat">0.0 s</p>
<button id="boshlash" type="button">Boshlash</button>
<button id="toxtatish" type="button">To'xtatish</button>
<button id="nolga" type="button">Nolga</button>
<script>
  const soat = document.getElementById("soat");
  let taymer = null;
  let boshi = 0;
  let toplangan = 0;

  function vaqtniYoz(ms) {
    soat.textContent = `${(ms / 1000).toFixed(1)} s`;
  }

  function yangila() {
    vaqtniYoz(toplangan + (Date.now() - boshi));
  }

  function toxtat() {
    if (taymer === null) {
      return;
    }
    clearInterval(taymer);
    taymer = null;
    toplangan += Date.now() - boshi;
  }

  const boshlash = document.getElementById("boshlash");
  boshlash.addEventListener("click", () => {
    if (taymer !== null) {
      return;
    }
    boshi = Date.now();
    taymer = setInterval(yangila, 100);
    console.log("Boshlandi. Taymer identifikatori:", taymer);
  });

  const toxtatish = document.getElementById("toxtatish");
  toxtatish.addEventListener("click", () => {
    toxtat();
    vaqtniYoz(toplangan);
  });

  document.getElementById("nolga").addEventListener("click", () => {
    toxtat();
    toplangan = 0;
    vaqtniYoz(0);
  });
</script>

Nimani ko'rish kerak:

  • Konsolda identifikator — son. Brauzer shunday.
  • «Boshlash» ni ikki marta bosish ikkinchi interval ochmaydi: if (taymer !== null) return; qatori himoya qiladi. Bu qator bo'lmasa, ikkita interval ochilardi va clearInterval faqat oxirgisini to'xtatardi — birinchisi "yo'qolib", abadiy ishlab qolardi.
  • Vaqt sanoq bilan emas, Date.now() ayirmasi bilan hisoblanadi. Nega — keyingi bo'limda.

Tekshirib ko'ring: Sekundomer kodida toxtat funksiyasi taymer = null qatorisiz yozilsa, "To'xtatish" dan keyin "Boshlash" ishlaydimi?

Javob

Yo'q. Interval to'xtaydi, lekin taymer da eski identifikator qoladi. Keyingi «Boshlash» taymer !== null ni ko'rib, darhol qaytadi. null — "hozir taymer yo'q" degan belgi. Taymerni to'xtatganda belgini ham tozalash kerak.

5. Nega taymer vaqt o'lchamaydi

setInterval(fn, 100) har 100 ms da chaqiriladi — demak 10 marta chaqirilsa, 1 soniya o'tgan, shundaymi? Yo'q, uch sabab bilan:

  1. Har chaqiruv biroz kech keladi — kechikishlar yig'iladi.
  2. Stek band bo'lsa, chaqiruvlar kutadi yoki o'tkazib yuboriladi.
  3. Yashirin tabda brauzer taymerlarni sekinlashtiradi — Sahifa hayot sikli darsida ko'rganimizdek, soniyada ko'pi bilan bir marta. Chrome tab 5 daqiqadan ko'p yashirin tursa, ichma-ich taymerlarni daqiqada bir martaga tushiradi.

Shuning uchun sekundomer chaqiruvlarni sanamaydi. U har safar haqiqiy vaqtni so'raydi: Date.now() - boshi. Taymer faqat "ekranni yangilash vaqti keldi" deb eslatadi. Bu qoida hamma joyda ishlaydi: vaqtni soatdan oling, taymerdan emas.

Tekshirib ko'ring: Sardor sekundomerni soniya++ bilan yozdi: setInterval har 1000 ms da soniya ni bittaga oshiradi. Mehmon boshqa tabga o'tib, 3 daqiqadan keyin qaytdi. Sekundomer to'g'ri vaqtni ko'rsatadimi?

Javob

Kafolat yo'q. Yashirin tabda brauzer taymerlarni sekinlashtiradi, band stekda chaqiruvlar kechikadi — soniya haqiqiy vaqtdan ortda qolishi mumkin. Date.now() - boshi esa har safar haqiqiy soatga qaraydi: taymer necha marta chaqirilganidan qat'i nazar, natija to'g'ri.

6. 0 ms va minimal kechikish

6.1 setTimeout(fn, 0) nimani anglatadi?

0 ms — "darhol" emas, balki "joriy kod tugashi bilan, imkon qadar tez":

js
setTimeout(() => console.log("taymer (0 ms)"), 0);

for (let i = 1; i <= 3; i++) {
  console.log(`sinxron ${i}`);
}

Konsolda:

text
sinxron 1
sinxron 2
sinxron 3
taymer (0 ms)

Callback navbatga tushadi va stek bo'shashini kutadi. Bu xulq foydali: "hozirgi ishni tugat, keyin shuni qil" deyish kerak bo'lsa, setTimeout(fn, 0) ishlatiladi. Navbat qanday ishlashini Event loop darsida aniq ko'ramiz.

6.2 Minimal kechikish: brauzer

Brauzer 0 ms ni har doim ham bajara olmaydi. HTML standartida qoida bor: taymer callback'i ichidan yana taymer qo'yilsa va bunday ichma-ich daraja 5 dan oshsa, kechikish kamida 4 ms ga ko'tariladi. Inglizcha bu clamping ("qisish") deyiladi. Maqsad — noto'g'ri yozilgan kod protsessorni 100% band qilib qo'ymasin.

Biz Chrome 154 da o'lchadik: setTimeout(fn, 0) callback'i o'zini yana setTimeout(fn, 0) bilan chaqiradi, har chaqiruvgacha o'tgan vaqt yoziladi. Ustunlarga qarang — qaysi chaqiruvdan boshlab vaqt sakraydi?

Ichma-ich setTimeout(fn, 0): chaqiruvgacha haqiqiy kechikish
  • 3-chaqiruv0 ms
  • 4-chaqiruv0 ms
  • 5-chaqiruv0 ms
  • 6-chaqiruv0 ms
  • 7-chaqiruveng kami 4,2 ms6 ms
  • 8-chaqiruveng kami 5,3 ms8,2 ms
  • 9-chaqiruveng kami 4,1 ms8,2 ms

Manba: O'lchandi: Chrome 154 (headless, Windows 11), 60 marta, mediana, 2026-10-05

Dastlabki oltita chaqiruv deyarli 0 ms da ishladi. Yettinchisidan boshlab kechikish 4 ms dan pastga tushmadi. Windows'da u 6–8 ms gacha chiqdi — operatsion tizim taymeri aniqligi ham qo'shiladi.

6.3 Minimal va maksimal kechikish: Node

Node'da qoida boshqacha. Node hujjatiga ko'ra, 1 dan kichik kechikish 1 ms ga aylanadi — ya'ni Node'da setTimeout(fn, 0) aslida setTimeout(fn, 1). Ichma-ich 4 ms qoidasi esa Node'da yo'q.

Yuqori chegara ham bor. Host kechikishni xotirada 32 bitli joyda saqlaydi — bu joyga sig'adigan eng katta musbat son 2 147 483 647. Demak eng uzun kechikish 2 147 483 647 ms — taxminan 24,8 kun. Undan katta son berilsa, ikkala host ham uni 1 ms deb oladi — ya'ni callback darhol ishlaydi:

js
setTimeout(() => console.log("Ishladi!"), 2 ** 31);
console.log("Taymer 2 ** 31 ms ga qo'yildi (≈ 24,9 kun)");

Konsolda:

text
Taymer 2 ** 31 ms ga qo'yildi (≈ 24,9 kun)
Ishladi!

Node bu haqda ogohlantiradi ham:

text
(node:9096) TimeoutOverflowWarning: 2147483648 does not fit into a 32-bit signed integer.
Timeout duration was set to 1.

Tarjimasi: "2147483648 soni 32 bitli ishorali butun songa sig'maydi. Taymer davomiyligi 1 qilib qo'yildi." Brauzer esa hech narsa demaydi — jim ishlatib yuboradi. Bir oydan keyingi eslatma kerak bo'lsa, taymer emas, server va ma'lumotlar bazasi kerak. Ularni 25–27-qismlarda ko'ramiz.

Tekshirib ko'ring: Brauzerda setTimeout(fn, 0) ning callback'i ichida yana setTimeout(fn, 0) — va shu zanjir 20 marta. Hammasi bo'lib taxminan qancha vaqt ketadi: 0 ms, ~60 ms yoki 20 soniya?

Javob

Taxminan 60 ms (Windows'da biroz ko'proq). Dastlabki oltita chaqiruv deyarli 0 ms, qolgan o'n to'rttasi kamida 4 ms dan: 14 × 4 = 56 ms. Nol bo'lmaydi — clamping bor. 20 soniya ham bo'lmaydi — bu faqat yashirin tabda, sekinlashtirish ishga tushganda mumkin.

7. Rekursiv setTimeout

7.1 setInterval ning zaif joyi

setInterval callback qancha ishlashidan qat'i nazar, chaqiruvlarni boshlanish vaqti bo'yicha rejalashtiradi. Callback og'ir bo'lsa, chaqiruvlar orasidagi "dam olish" qisqaradi yoki umuman yo'qoladi.

Rekursiv setTimeout — callback o'zining oxirida keyingi chaqiruvni o'zi rejalashtiradi. Shunda oraliq har doim oldingi ish tugagandan keyin sanaladi.

O'lchaymiz. Ikkala usulda ham kechikish 200 ms, har chaqiruv esa 100 ms og'ir ish qiladi (bandQil — o'tgan darsdan). Chaqiruvlar boshlanishi orasidagi vaqtni yozamiz:

js
function bandQil(ms) {
  const boshi = Date.now();
  while (Date.now() - boshi < ms) {}
}
const yaxlitla = (ms) => Math.round(ms / 100) * 100;

let n = 0;
let oldingi = Date.now();
const oraliqlar = [];
const id = setInterval(() => {
  oraliqlar.push(yaxlitla(Date.now() - oldingi));
  oldingi = Date.now();
  bandQil(100);
  n++;
  if (n === 4) {
    clearInterval(id);
    console.log("setInterval:", oraliqlar.join(" "));
    rekursivniBoshla();
  }
}, 200);

function rekursivniBoshla() {
  let m = 0;
  let avvalgi = Date.now();
  const oraliq = [];
  function qadam() {
    oraliq.push(yaxlitla(Date.now() - avvalgi));
    avvalgi = Date.now();
    bandQil(100);
    m++;
    if (m < 4) {
      setTimeout(qadam, 200);
    } else {
      console.log("rekursiv setTimeout:", oraliq.join(" "));
    }
  }
  setTimeout(qadam, 200);
}

Konsolda:

text
setInterval: 200 200 200 200
rekursiv setTimeout: 200 300 300 300

setInterval har 200 ms da boshlanadi: 100 ms ish + atigi 100 ms dam. Rekursiv usulda boshlanishlar orasida 300 ms: 100 ms ish + to'liq 200 ms dam. Ish 200 ms dan uzoq davom etsa, setInterval da dam umuman qolmaydi — chaqiruvlar birin-ketin "yopishib" ketadi.

7.2 O'zgaruvchan kechikish

Rekursiv usulning ikkinchi afzalligi — har safar kechikishni o'zgartirish mumkin. Masalan, server javob bermasa, qayta urinishlar orasini ikki barobardan oshirib borish odat. Bu eksponensial kutish (exponential backoff) deyiladi:

js
let urinish = 1;
let kechikish = 100;

function ulanishgaUrin() {
  console.log(`${urinish}-urinish (${kechikish} ms kutildi)`);
  if (urinish === 4) {
    console.log("Ulandi!");
    return;
  }
  urinish++;
  kechikish *= 2;
  setTimeout(ulanishgaUrin, kechikish);
}

setTimeout(ulanishgaUrin, kechikish);

Konsolda:

text
1-urinish (100 ms kutildi)
2-urinish (200 ms kutildi)
3-urinish (400 ms kutildi)
4-urinish (800 ms kutildi)
Ulandi!

setInterval bilan buni qilib bo'lmaydi — uning oralig'i bir marta beriladi va o'zgarmaydi. Haqiqiy server bilan qayta urinishni Asinxron naqshlar darsida yozamiz.

setInterval Rekursiv setTimeout
Oraliq sanaladi boshlanishdan boshlanishgacha tugashdan keyingi boshlanishgacha
Og'ir callback dam qisqaradi dam saqlanadi
Kechikishni o'zgartirish yo'q har safar
To'xtatish clearInterval keyingisini qo'ymaslik yoki clearTimeout

Maslahat: Callback qisqa bo'lsa va aniq "har N ms" kerak bo'lsa (soat, sekundomer) — setInterval. Callback og'ir yoki asinxron bo'lsa (serverdan so'rash), yoki oraliq o'zgarsa — rekursiv setTimeout.

Tekshirib ko'ring: Eksponensial kutish misolida urinish === 4 sharti bo'lmasa, beshinchi urinish necha millisekund kutib keladi?

Javob

1600 ms. Har urinishdan keyin kechikish *= 2 qatori oraliqni ikki barobar oshiradi: 100, 200, 400, 800, keyin 1600. Rekursiv setTimeout har safar shu yangi qiymat bilan keyingi taymerni qo'yadi — setInterval buni qila olmasdi.

8. Taymer callback'ida this

Obyekt metodini taymerga berish — this yo'qolishi darsidagi tuzoq:

js
const soat = {
  nomi: "Bahor soati",
  korsat() {
    console.log(this.nomi);
  },
};

setTimeout(soat.korsat, 100);
setTimeout(() => soat.korsat(), 200);

Konsolda:

text
undefined
Bahor soati

Birinchi taymer metodni obyektsiz chaqirdi. Brauzerda this — window, Node'da esa Timeout obyekti. Ikkalasida ham nomi yo'q. Arrow funksiyaga o'rash (yoki bind, call, apply, bind) muammoni hal qiladi.

9. Ko'p uchraydigan xatolar

9.1 Funksiya o'rniga chaqiruv natijasi

setTimeout(ogohlantir(), 1000) — ogohlantir darhol ishlaydi, taymerga esa undefined beriladi. Node bu holatda xato tashlaydi, brauzer esa jim o'tkazib yuboradi. Tuzatish: qavssiz setTimeout(ogohlantir, 1000) yoki argument kerak bo'lsa — setTimeout(() => ogohlantir("Salom"), 1000).

9.2 Matn (satr) berish

js
setTimeout("console.log('Salom')", 100);
text
TypeError [ERR_INVALID_ARG_TYPE]: The "callback" argument must be of type function. Received type string ('console.log('Salom')')

Tarjimasi: "callback argumenti funksiya bo'lishi kerak. Satr keldi." Brauzer esa satrni qabul qiladi va uni kod sifatida bajaradi — xuddi eval kabi (eval va new Function). Bu eski usul, sekin va xavfli: satr ichiga begona kod tushib qolishi mumkin. Eski kodda uchratasiz — hech qachon yozmang.

9.3 To'xtatilmagan interval

Komponent yoki oyna yopildi, lekin setInterval ishlashda davom etdi: u yo'q elementni yangilashga urinadi, xotira egallaydi, Node'da esa dastur tugamaydi. Tuzatish: har setInterval uchun "qayerda to'xtaydi?" degan savolga javob bo'lsin — clearInterval qatorini interval bilan birga yozing.

9.4 Identifikatorni yo'qotish

js
let taymer = setInterval(() => {}, 1000);
taymer = setInterval(() => {}, 1000); // birinchisi "yo'qoldi"
clearInterval(taymer);
console.log("Ikkinchisi to'xtadi, birinchisi — yo'q");

Bu kod hech qachon tugamaydi — birinchi intervalning identifikatori ustidan yozildi va uni endi hech kim to'xtata olmaydi. Uni ishga tushirmang: Node'da Ctrl+C gina to'xtatadi. Sekundomerdagi if (taymer !== null) return; himoyasi aynan shu uchun.

9.5 Taymer bilan vaqt o'lchash

soniya++ ni har 1000 ms da qilib, "o'tgan vaqt" deb ko'rsatish — yashirin tabda va band stekda ortda qoladi. Tuzatish: vaqtni Date.now() ayirmasidan oling.

10. Mashqlar

1-mashq (oson): Choy damlash

setTimeout va qo'shimcha argumentlar bilan uchta xabar chiqaring: 300 ms da "Suv qaynadi", 600 ms da "Choy solindi", 900 ms da "Choy tayyor". Uchala taymer uchun bitta xabarYoz(matn) funksiyasidan foydalaning — arrow'siz.

Yechim
js
function xabarYoz(matn) {
  console.log(matn);
}

setTimeout(xabarYoz, 900, "Choy tayyor");
setTimeout(xabarYoz, 300, "Suv qaynadi");
setTimeout(xabarYoz, 600, "Choy solindi");

Konsolda:

text
Suv qaynadi
Choy solindi
Choy tayyor

Taymerlarni ataylab aralash tartibda yozdik — chiqish tartibi kod tartibiga emas, kechikishga bog'liq. Kechikishdan keyingi argument ("Choy tayyor") xabarYoz ning matn parametriga tushadi.

2-mashq (o'rta): Intervalni rekursiv taymerga aylantiring

Quyidagi kod har 250 ms da buyurtma holatini tekshiradi va 3-tekshiruvda to'xtaydi. Uni setIntervalsiz — rekursiv setTimeout bilan qayta yozing. Chiqish bir xil qolsin.

js
let tekshiruv = 0;

const id = setInterval(() => {
  tekshiruv++;
  console.log(`${tekshiruv}-tekshiruv: manti pishmoqda`);
  if (tekshiruv === 3) {
    clearInterval(id);
    console.log("Manti tayyor!");
  }
}, 250);
Yechim
js
let tekshiruv = 0;

function tekshir() {
  tekshiruv++;
  console.log(`${tekshiruv}-tekshiruv: manti pishmoqda`);
  if (tekshiruv === 3) {
    console.log("Manti tayyor!");
    return;
  }
  setTimeout(tekshir, 250);
}

setTimeout(tekshir, 250);

Konsolda:

text
1-tekshiruv: manti pishmoqda
2-tekshiruv: manti pishmoqda
3-tekshiruv: manti pishmoqda
Manti tayyor!

To'xtatish uchun clearTimeout ham kerak bo'lmadi — keyingi taymerni qo'ymaslik kifoya. Funksiyaga nom berish shart: o'zini chaqirish uchun u nomini bilishi kerak.

3-mashq (qiyin): Bekor qilinadigan takrorlash

takrorla(fn, ms, marta) funksiyasini yozing. U fn ni har ms da, ko'pi bilan marta marta chaqirsin (rekursiv setTimeout bilan). fn ga chaqiruv tartib raqami (1, 2, 3 ...) berilsin. takrorla to'xtatish funksiyasini qaytarsin: uni chaqirsa, qolgan chaqiruvlar bo'lmasin. Ishora: keyingi taymer identifikatori closure'da saqlansin (Closure amaliyotda dagi debounce ni eslang).

Sinov: marta = 5, ms = 200, to'xtatish 650 ms da chaqirilsin.

Yechim
js
function takrorla(fn, ms, marta) {
  let tartib = 0;
  let taymer = null;

  function qadam() {
    tartib++;
    fn(tartib);
    if (tartib < marta) {
      taymer = setTimeout(qadam, ms);
    }
  }

  taymer = setTimeout(qadam, ms);
  return () => clearTimeout(taymer);
}

const toxtat = takrorla(
  (n) => console.log(`${n}-qo'ng'iroq: buyurtma tayyormi?`),
  200,
  5,
);

setTimeout(() => {
  toxtat();
  console.log("Mehmon o'zi keldi — qo'ng'iroqlar to'xtatildi");
}, 650);

Konsolda:

text
1-qo'ng'iroq: buyurtma tayyormi?
2-qo'ng'iroq: buyurtma tayyormi?
3-qo'ng'iroq: buyurtma tayyormi?
Mehmon o'zi keldi — qo'ng'iroqlar to'xtatildi

Uch qo'ng'iroq 200, 400 va 600 ms da bo'ldi. Keyin 650 ms da toxtat() 800 ms ga qo'yilgan to'rtinchi taymerni bekor qildi. O'zgaruvchi taymer har qadamda yangilanadi — shuning uchun qaytarilgan funksiya doim oxirgi rejalashtirilgan taymerni to'xtatadi. Bu closure: takrorla allaqachon tugagan, lekin uning taymer o'zgaruvchisi yashashda davom etadi.

Faylni kurs/mashqlar/11/02-taymerlar/takrorla.mjs ga saqlang:

bash
git add 11/02-taymerlar/takrorla.mjs
git commit -m "11/02: bekor qilinadigan takrorla"

4-mashq: Vazifalar qadami — o'zi yo'qoladigan e'lon

vazifalar dagi #elon (role="status") "«Non olish» qo'shildi." kabi xabarlarni ko'rsatadi. Hozir xabar keyingi amalgacha ekranda qolib ketadi. Endi:

  1. Yangi branch: git switch -c feature/elon-taymer.
  2. render.js dagi elonQil(matn) e'lonni 3 soniyadan keyin tozalasin.
  3. Ketma-ket e'lonlar to'g'ri ishlasin: birinchi xabardan 2 soniya o'tib ikkinchisi chiqsa, ikkinchisi to'liq 3 soniya tursin — birinchisining taymeri uni erta o'chirmasin.
  4. elonQil("") (asosiy.js uni bir necha joyda chaqiradi) — e'lonni darhol tozalasin va yangi taymer qo'ymasin.

Ishora: taymer identifikatori funksiyadan tashqarida, modul darajasida saqlansin; har yangi e'londan oldin eski taymer clearTimeout bilan bekor qilinsin. clearTimeout(null) xavfsiz — tekshirish shart emas.

Yechim

Avval xatoni ko'rib olamiz. clearTimeout siz yozilgan variantni Node'da sinaymiz: sahifadagi #elon o'rniga oddiy obyekt, muddat esa sinash uchun 400 ms:

js
const elon = { textContent: "" };
const ELON_MUDDATI = 400;

function elonQil(matn) {
  elon.textContent = matn;
  setTimeout(() => {
    elon.textContent = "";
  }, ELON_MUDDATI);
}

const qara = (ms) => console.log(`${ms} ms: «${elon.textContent}»`);

elonQil("Non olish qo'shildi.");
setTimeout(() => elonQil("Choy qo'shildi."), 200);
setTimeout(qara, 100, 100);
setTimeout(qara, 500, 500);

Konsolda:

text
100 ms: «Non olish qo'shildi.»
500 ms: «»

"Choy" xabari 200 ms da chiqdi va 600 ms gacha turishi kerak edi. Lekin 400 ms da birinchi e'lonning taymeri ishladi va uni erta o'chirib yubordi. To'g'ri variant:

js
const elon = { textContent: "" };
const ELON_MUDDATI = 400;
let elonTaymeri = null;

function elonQil(matn) {
  clearTimeout(elonTaymeri);
  elon.textContent = matn;
  if (matn === "") {
    return;
  }
  elonTaymeri = setTimeout(() => {
    elon.textContent = "";
  }, ELON_MUDDATI);
}

const qara = (ms) => console.log(`${ms} ms: «${elon.textContent}»`);

elonQil("Non olish qo'shildi.");
setTimeout(() => elonQil("Choy qo'shildi."), 200);
setTimeout(qara, 100, 100);
setTimeout(qara, 500, 500);
setTimeout(qara, 700, 700);

Konsolda:

text
100 ms: «Non olish qo'shildi.»
500 ms: «Choy qo'shildi.»
700 ms: «»

Endi har yangi e'lon eski taymerni bekor qiladi va o'zinikini qo'yadi. Ilovadagi render.js — xuddi shu, faqat haqiqiy element va 3000 ms bilan:

js
// render.js — e'lon 3 soniyadan keyin o'zi yo'qoladi
const ELON_MUDDATI = 3000;
let elonTaymeri = null;

export function elonQil(matn) {
  clearTimeout(elonTaymeri);
  elon.textContent = matn;
  if (matn === "") {
    return;
  }
  elonTaymeri = setTimeout(() => {
    elon.textContent = "";
  }, ELON_MUDDATI);
}

ELON_MUDDATI va elonTaymeri eksport qilinmaydi — ular render.js ning ichki ishi. asosiy.js o'zgarmaydi: u avvalgidek elonQil(...) ni chaqiradi.

Brauzerda sinang:

  • Vazifa qo'shing — e'lon 3 soniyada yo'qoladi.
  • Ikki vazifani 1–2 soniya oralig'ida qo'shing — ikkinchi e'lon to'liq 3 soniya turadi.
  • Tahrirlashni boshlang (elonQil("")) — e'lon darhol yo'qoladi.
  • NVDA bilan: e'lon avvalgidek o'qiladi. Tozalanganda ekran o'quvchi hech narsa demaydi — bo'sh matn e'lon qilinmaydi.

Tahrirdagi xato xabari ham elonQil orqali chiqadi va 3 soniyada yo'qoladi. Bu maqbul: maydon aria-invalid="true" bo'lib qoladi va «Saqlash» qayta bosilsa, xabar yana chiqadi.

bash
git add assets/js/render.js
git commit -m "Render: e'lonni 3 soniyadan keyin tozala"
git switch main
git merge feature/elon-taymer
git branch -d feature/elon-taymer
git push

11. Real ishda

  • Bildirishnomalar (toast). Telegram Web, Click ilovasining veb-versiyasi — "Nusxa olindi", "To'lov qabul qilindi" kabi xabarlar bir necha soniyada yo'qoladi. Ichida — setTimeout va clearTimeout, xuddi «Vazifalar qadami» dagidek.
  • Debounce va throttle. Qidiruv maydonida har harfga emas, yozish to'xtaganda so'rov yuborish. Closure amaliyotda dagi debounce taymerga qurilgan; to'liq mavzu — Debounce va throttle.
  • Animatsiya. Harakatni setInterval bilan qilish eskirgan usul: kadrlar ekran yangilanishiga mos tushmaydi. Buning uchun requestAnimationFrame bor — requestAnimationFrame, 13-qismda.
  • Qayta urinish. Server javob bermasa — eksponensial kutish bilan qayta so'rash. Axios, fetch o'ramlari va to'lov tizimlari shunday qiladi.
  • Intervyu. "setTimeout(fn, 0) nima qiladi?", "setInterval va rekursiv setTimeout farqi?", "Taymer nega aniq ishlamaydi?" — juda ko'p beriladi.

Xulosa

  • setTimeout(fn, ms, ...argumentlar) — bir marta, kamida ms dan keyin; setInterval — har ms da, to'xtatilguncha.
  • Ikkalasi identifikator qaytaradi: brauzerda son, Node'da Timeout obyekti. Uni clearTimeout/clearInterval ga bering; noto'g'ri yoki eskirgan identifikator xato bermaydi.
  • 0 ms — "joriy kod tugagach". Brauzerda ichma-ich taymerlar kamida 4 ms ga qisiladi, Node'da 0 → 1 ms; 24,8 kundan katta kechikish 1 ms bo'lib qoladi.
  • Taymer vaqtni o'lchamaydi — vaqtni Date.now() dan oling.
  • Og'ir yoki o'zgaruvchan oraliqli takrorlash uchun — rekursiv setTimeout.
  • Har taymer uchun "qayerda to'xtaydi?" savoliga javob bo'lsin.

Keyingi dars: Callbacklar va callback hell — asinxron natijani callback bilan olish, Node'ning "avval xato" kelishuvi va ketma-ket asinxron ishlar nega "piramida"ga aylanishini ko'ramiz.

Manbalar

  • HTML Living Standard: "Timers" (setTimeout, nesting level va 4 ms qoidasi) — html.spec.whatwg.org
  • MDN: "setTimeout()", "setInterval()", "clearTimeout()" — developer.mozilla.org
  • Node.js 24 hujjatlari: "Timers" (setTimeout, Timeout klassi, unref) — nodejs.org/api/timers.html
  • Chrome for Developers: "Heavy throttling of chained JS timers beginning in Chrome 88" — developer.chrome.com/blog
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript taymerlari: setTimeout va setInterval — IlmHamroh