7-bo‘lim

Hodisalar sikli

Sinxron va asinxron kod tartibi, mikro va makro vazifalar, setTimeout va setImmediate, nextTick ning ESM dagi kutilmagan o'rni va siklni bloklash.

🕑 12 daqiqa o‘qish 📄 746 so‘z 👁 0 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Sinxron avval
  2. Ikki xil navbat
  3. nextTick va ESM
  4. setTimeout va setImmediate
  5. await tartibni o'zgartiradi
  6. Siklni bloklash
  7. Navbatni bo'shatish
  8. Taymerlar aniq emas
  9. Xulosa

Node bitta oqimda ishlaydi, lekin bir vaqtda minglab so'rovga javob bera oladi. Buni hodisalar sikli (event loop) ta'minlaydi.

Bu bo'lim Node ning eng chalkash qismi haqida - lekin uni tushunish keyingi hamma narsani osonlashtiradi.

Sinxron avval #

JavaScript
console.log("1");

setTimeout(() => console.log("3 - setTimeout"), 0);

Promise.resolve().then(() => console.log("2 - promise"));

console.log("1.5");
Natija
1
1.5
2 - promise
3 - setTimeout

Qoida oddiy: hamma sinxron kod birinchi tugaydi. Faqat shundan keyin navbatdagi vazifalar bajariladi.

setTimeout(..., 0) "darhol" degani emas - u "sinxron kod tugagach, birinchi imkoniyatda" degani.

Ikki xil navbat #

Bitta aylanish qanday o'tadi 1. Sinxron kod butun fayl oxirigacha ishlaydi - hech narsa uni bo'lolmaydi 2. Mikro vazifalar - navbat BO'SHAGUNCHA Promise.then, await, queueMicrotask, process.nextTick yangi mikro vazifa qo'shilsa, u ham SHU yerda bajariladi 3. Makro vazifalar - har aylanishda BITTA guruh setTimeout, setInterval, setImmediate, kirish-chiqish javoblari har bir makro vazifadan keyin mikro navbat yana bo'shatiladi so'ng sikl yangidan boshlanadi mikro vazifalar makro vazifalardan har doim oldin
JavaScript
console.log("boshlandi");

setTimeout(() => console.log("makro: setTimeout"), 0);

Promise.resolve()
    .then(() => console.log("mikro: birinchi"))
    .then(() => console.log("mikro: ikkinchi"));

queueMicrotask(() => console.log("mikro: queueMicrotask"));

console.log("tugadi");
Natija
boshlandi
tugadi
mikro: birinchi
mikro: queueMicrotask
mikro: ikkinchi
makro: setTimeout

Diqqat qiling: mikro: ikkinchi queueMicrotask dan keyin chiqdi.

Sababi - zanjir: birinchi .then bajarilgach, ikkinchisi navbatga yangi qo'shiladi, queueMicrotask esa allaqachon navbatda turgan edi.

nextTick va ESM #

Endi Node ning eng chalkash burchagi.

JavaScript
const izlar = [];

Promise.resolve().then(() => izlar.push("promise"));
queueMicrotask(() => izlar.push("queueMicrotask"));
process.nextTick(() => izlar.push("nextTick"));

setTimeout(() => console.log(izlar.join(" -> ")), 10);
Natija
promise -> queueMicrotask -> nextTick
Ko'p qo'llanmada bu boshqacha yozilgan

Deyarli hamma manbada shunday deyiladi: process.nextTick hamma mikro vazifalardan oldin bajariladi.

Bu CommonJS uchun to'g'ri. .cjs faylida yuqoridagi kod nextTick -> promise -> queueMicrotask beradi.

ESM da esa natija teskari. Sabab: ESM moduli o'zi mikro vazifa ichida bajariladi. Modul tanasi ishlayotganda joriy mikro navbat hali bo'shatilmagan - shuning uchun u avval oxirigacha bo'shatiladi, nextTick navbatiga esa faqat keyin o'tiladi.

Bu faqat modulning eng yuqori darajasida shunday. Xuddi shu kod funksiya ichida - masalan setTimeout yoki so'rov ishlovchisida - odatiy tartibni beradi: nextTick birinchi.

Amaliy xulosa: process.nextTick ga tayangan kod yozmang. Uning Promise dan farqi juda nozik va kontekstga bog'liq. Deyarli har doim queueMicrotask yoki oddiy Promise yetarli.

Buni isbotlaymiz - xuddi shu kodni funksiya ichiga qo'yamiz:

JavaScript
const izlar = [];

setTimeout(() => {
    Promise.resolve().then(() => izlar.push("promise"));
    queueMicrotask(() => izlar.push("queueMicrotask"));
    process.nextTick(() => izlar.push("nextTick"));
}, 0);

setTimeout(() => console.log(izlar.join(" -> ")), 20);
Natija
nextTick -> promise -> queueMicrotask

Bir xil uch qator, boshqa tartib - farq faqat qayerda yozilganida.

setTimeout va setImmediate #

JavaScript
import { readFile } from "node:fs/promises";

const izlar = [];

await readFile(new URL(import.meta.url));

setTimeout(() => izlar.push("setTimeout"), 0);
setImmediate(() => izlar.push("setImmediate"));

setTimeout(() => console.log(izlar.join(" -> ")), 20);
Natija
setImmediate -> setTimeout

setImmediate - Node ga xos funksiya (brauzerda yo'q). U "joriy aylanish tugagach darhol" degani.

FunksiyaQachon ishlaydi
setTimeout(f, 0)Taymerlar bosqichida, keyingi aylanishda
setImmediate(f)Joriy aylanishning check bosqichida
process.nextTick(f)Joriy amaldan keyin darhol
queueMicrotask(f)Mikro navbatda
Asosiy modulda tartib kafolatlanmaydi

Kirish-chiqish ishlovchisi ichida setImmediate har doim setTimeout(f, 0) dan oldin ishlaydi - bu kafolatlangan.

Lekin ikkalasini dasturning eng boshida, hech qanday kirish-chiqishsiz yozsangiz, tartib kafolatlanmaydi: u jarayonning ishga tushish tezligiga bog'liq bo'lib qoladi. Bir xil kod bir kompyuterda bir xil, boshqasida boshqacha chiqishi mumkin.

Shuning uchun yuqoridagi misolda avval fayl o'qildi - shunda natija barqaror bo'ladi.

Umumiy maslahat: bu ikkisining tartibiga tayanmang. Tartib muhim bo'lsa, uni await bilan ochiq belgilang.

await tartibni o'zgartiradi #

JavaScript
async function ishla(nom) {
    console.log(`${nom}: boshlandi`);
    await null;
    console.log(`${nom}: davom etdi`);
}

console.log("oldin");
ishla("A");
ishla("B");
console.log("keyin");
Natija
oldin
A: boshlandi
B: boshlandi
keyin
A: davom etdi
B: davom etdi

async funksiya darhol ishlay boshlaydi - birinchi await gacha sinxron davom etadi.

await ga yetganda funksiya to'xtaydi va boshqaruv chaqiruvchiga qaytadi. Qolgan qismi mikro vazifa sifatida navbatga qo'yiladi.

Aynan shuning uchun "A: boshlandi" va "B: boshlandi" oldin, davomlari esa keyin chiqdi.

Siklni bloklash #

JavaScript
function ogirHisob(n) {
    let jami = 0;
    for (let i = 0; i < n; i++) jami += i;
    return jami;
}

const izlar = [];

setTimeout(() => izlar.push("taymer"), 0);

const natija = ogirHisob(5_000_000);
izlar.push("hisob tugadi");

setTimeout(() => {
    console.log(izlar.join(" -> "));
    console.log("Natija musbat:", natija > 0);
}, 10);
Natija
hisob tugadi -> taymer
Natija musbat: true
Bu Node ning eng jiddiy xavfi

setTimeout(..., 0) birinchi yozilgan edi, lekin u hisobdan keyin ishladi.

Sabab: hodisalar sikli sinxron kod tugashini kutadi. Besh million qadamlik sikl ishlayotganda Node hech narsa qila olmaydi - na taymer, na yangi so'rov, na fayl javobi.

Haqiqiy serverda bu shuni bildiradi: bitta og'ir funksiya hamma foydalanuvchini kutishga majbur qiladi.

Nima qilish kerak:

  1. Og'ir ishni bo'lib tashlang - har bo'lakdan keyin siklga nafas bering.
  2. worker_threads - alohida oqimda hisoblang.
  3. Boshqa jarayonga bering - navbat yoki alohida xizmat.

Eng muhimi - buni bilish. Node "sekin" emas; u shunchaki kutish uchun optimallashtirilgan, hisob uchun emas.

JavaScript
async function bolibIshla(jami, bolak) {
    let natija = 0;
    let uzilishlar = 0;

    for (let i = 0; i < jami; i++) {
        natija += i;
        if (i > 0 && i % bolak === 0) {
            await new Promise((yechim) => setImmediate(yechim));
            uzilishlar++;
        }
    }
    return { natija, uzilishlar };
}

const { natija, uzilishlar } = await bolibIshla(1000, 250);

console.log("Natija:", natija);
console.log("Uzilishlar:", uzilishlar);
Natija
Natija: 499500
Uzilishlar: 3

await new Promise((yechim) => setImmediate(yechim)) - siklga "nafas olish" imkonini beradi.

Har uzilishda Node kutayotgan boshqa ishlarni bajarib oladi va keyin davom etadi.

Taymerlar aniq emas #

JavaScript
const kechikish = 50;

const boshi = process.hrtime.bigint();

setTimeout(() => {
    const otgan = Number(process.hrtime.bigint() - boshi) / 1e6;
    console.log("Kamida so'ralgancha kutdi:", otgan >= kechikish);
    console.log("Aniq songa teng emas:", otgan !== kechikish);
}, kechikish);
Natija
Kamida so'ralgancha kutdi: true
Aniq songa teng emas: true

setTimeout(f, 50) - "aynan 50 millisekunddan keyin" degani emas, balki "kamida 50 millisekunddan keyin" degani.

Agar o'sha paytda sikl band bo'lsa, taymer kechikadi. Shuning uchun Node da aniq vaqtga tayanib bo'lmaydi.

Amaliy topshiriq
  1. Sinxron va setTimeout tartibini kuzating.
  2. Promise.then zanjirining tartibini chizib chiqing.
  3. nextTick ni ESM ning yuqori darajasida sinang.
  4. Xuddi shuni setTimeout ichida qayta sinang.
  5. Ikkalasini .cjs faylida ham tekshiring.
  6. Kirish-chiqishdan keyin setImmediate va setTimeout ni solishtiring.
  7. Ikki async funksiyani ketma-ket chaqirib, tartibni ko'ring.
  8. Og'ir sikl bilan taymerni kechiktiring.
  9. setImmediate bilan siklga nafas bering.
  10. Taymer aniq vaqtdan kechikishini o'lchang.

Xulosa #

  • Sinxron kod har doim birinchi tugaydi.
  • Mikro vazifalar (Promise, queueMicrotask) makro vazifalardan (setTimeout) oldin, va navbat butunlay bo'shatiladi.
  • process.nextTick ESM ning yuqori darajasida mikro vazifalardan keyin ishlaydi - CommonJS da esa oldin. Unga tayanmang.
  • setImmediate kirish-chiqish ichida setTimeout(f, 0) dan oldin; asosiy modulda esa tartib kafolatlanmaydi.
  • async funksiya birinchi await gacha sinxron ishlaydi.
  • Og'ir hisob butun serverni bloklaydi - bu Node ning asosiy xavfi.
  • Uzoq siklni await setImmediate bilan bo'lib, siklga nafas bering.
  • setTimeout kamida so'ralgancha kutadi, aniq emas.

Keyingi bo'limda hodisalar va EventEmitter ni o'rganamiz.

Xatolik topdingizmi?

Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.