Event Loop

"Macrotasks և Microtasks: Ինչպե՞ս է աշխատում JavaScript-ի շարժիչը:"

JavaScript-ի գործարկման հիմքում ընկած է Event Loop (Իրադարձությունների ցիկլ) գաղափարը: Այն շատ պարզ է՝ կա անվերջ ցիկլ, որտեղ JavaScript-ի շարժիչը սպասում է առաջադրանքների (tasks), կատարում է դրանք, իսկ հետո "քնում է"՝ սպասելով նոր առաջադրանքների:

UI Rendering: Կարևոր է իմանալ, որ բրաուզերը երբեք չի նկարում (render) էկրանը, քանի դեռ շարժիչը կատարում է որևէ առաջադրանք: Փոփոխությունները արտացոլվում են միայն այն ժամանակ, երբ առաջադրանքն ավարտվում է:

Macrotasks (Մակրոթասքեր)

Սրանք հիմնական առաջադրանքներն են, որոնք գալիս են բրաուզերից կամ արտաքին միջավայրից.

  • script (ամբողջական ֆայլի բեռնում և գործարկում)
  • Օգտատիրոջ գործողություններ (mousemove, click)
  • Ժամանակացույցեր (setTimeout, setInterval)

Microtasks (Միկրոթասքեր)

Միկրոթասքերը ստեղծվում են հենց մեր կոդի կողմից: Դրանք ավելի բարձր առաջնահերթություն ունեն, քան մակրոթասքերը.

  • Promise-ներ (.then, .catch, .finally)
  • await գործողություններ
  • queueMicrotask(func)

Կատարման հերթականությունը

Անմիջապես յուրաքանչյուր Macrotask ավարտելուց հետո, շարժիչը կատարում է ԲՈԼՈՐ Microtask-երը, նախքան որևէ այլ macrotask վերցնելը կամ UI-ը թարմացնելը (rendering):

graph TD Start["Event Loop Start"] e1@--> GetMacro["Get Oldest Macrotask"] GetMacro e2@--> RunMacro["Execute Macrotask"] RunMacro e3@--> CheckMicro{"Are there Microtasks?"} CheckMicro e4@-->|"Yes"| RunMicro["Execute Microtask"] RunMicro e5@--> CheckMicro CheckMicro e6@-->|"No"| Render["Render UI"] Render e7@--> CheckQueue{"Are there other Macrotasks?"} CheckQueue e8@-->|"No"| Sleep["Sleep"] Sleep e9@--> CheckQueue CheckQueue e10@-->|"Yes"| GetMacro e1@{ animate: true } e2@{ animate: true } e3@{ animate: true } e4@{ animate: true } e5@{ animate: true } e6@{ animate: true } e7@{ animate: true } e8@{ animate: true } e9@{ animate: true } e10@{ animate: true } style CheckMicro fill:#f7df1e,color:#000 style Render fill:#61afef,color:#000

Ի՞նչ հերթականությամբ կտպվեն թվերը console-ում:

console.log(1);

setTimeout(() => console.log(2));

Promise.resolve().then(() => console.log(3));

Promise.resolve().then(() => setTimeout(() => console.log(4)));

Promise.resolve().then(() => console.log(5));

setTimeout(() => console.log(6));

console.log(7);

Պատասխան և Բացատրություն

Արդյունքը կլինի՝ 1 7 3 5 2 6 4

  • 1 և 7 - Տպվում են անմիջապես, քանի որ սինխրոն կոդ են:
  • 3 և 5 - Տպվում են հաջորդիվ, քանի որ Microtask-եր են (Promise-ների .then): Ի դեպ, 4-րդ տողի Promise-ը գցում է նոր setTimeout(4) Macrotask հերթի մեջ:
  • 2, 6 և 4 - Տպվում են ամենավերջում, քանի որ Macrotask-եր են:

1. CPU ծանրաբեռնող խնդիրների բաժանում

Եթե ունեք շատ ծանր ցիկլ (օրինակ՝ գույների փոփոխություն մեծ տեքստում կամ 1 միլիարդ հաշվարկ), բրաուզերը կկախի ("hang"), և կոճակները չեն աշխատի: Կոդը կարելի է բաժանել մասերի և յուրաքանչյուր մասը դնել setTimeout(func)-ի մեջ: Այդպիսով, Event Loop-ը «շունչ կքաշի» և կկարողանա այլ բաներ անել (օրինակ՝ արձագանքել click-երին) կտորների արանքում:

2. Progress Bar Ցուցադրում

Քանի որ DOM-ի render-ը տեղի է ունենում միայն Task-ի ավարտից հետո, եթե հսկայական հաշվարկ անեք մեկ Task-ի ընթացքում, օգտատերը միայն վերջնական արդյունքը կտեսնի: Եթե հաշվարկը բաժանեք setTimeout-ներով, բրաուզերը կհասցնի թարմացնել էկրանը և ցույց տալ գործընթացը (progress):