మీ వెబ్ పేజీని నడిపించే JavaScript event loop, Node.js సర్వర్ను నడిపించే దానికంటే చాలా భిన్నంగా ఉంటుంది. మీరు జాగ్రత్తగా లేకపోతే, ఈ తేడా UIని ఫ్రీజ్ చేయవచ్చు లేదా I/O పనితీరును దెబ్బతీయవచ్చు. ఈ రెండు వాతావరణాలలో (environments) నడిచే async కోడ్ను రాసే ఎవరికైనా, ఇవి ఎక్కడ విభజించబడ్డాయో తెలియడం చాలా అవసరం.
ఈ తేడా ఎందుకు ముఖ్యం
Event loop అనేది ECMAScript spec ద్వారా నిర్వచించబడదు; ఇది host (బ్రౌజర్ లేదా Node.js) లో ఉంటుంది. బ్రౌజర్లు ఫ్రేమ్లను రెండర్ చేస్తున్నప్పుడు పేజీ స్పందించేలా (responsive) ఉంచాలి, అయితే Node.js నాన్-బ్లాకింగ్ I/O చుట్టూ నిర్మించబడింది. ఒక host లో పనిచేసే పద్ధతులను మరొక దానితో కలిపి వాడటం వల్ల తిరిగి కనిపెట్టలేని (hard to reproduce) బగ్లు రావచ్చు: ఒక పొడవైన promise chain బ్రౌజర్ యొక్క repaint ప్రక్రియను ఆపివేయవచ్చు, అదే సమయంలో సరిగ్గా నియంత్రించబడని process.nextTick లూప్ Node.js తన I/O ఫేజ్లకు చేరుకోకుండా అడ్డుకోవచ్చు.
బ్రౌజర్ యొక్క turn-based loop
బ్రౌజర్లో, ఈ లూప్ టాస్క్ ఎగ్జిక్యూషన్, microtask draining మరియు రెండరింగ్ను కలిపి ఒకే సైకిల్లో నిర్వహిస్తుంది:
- ఒక macrotaskను రన్ చేస్తుంది (ఉదాహరణకు: click handler,
setTimeout, మొదలైనవి). - అన్ని microtasksలను పూర్తి చేస్తుంది (promises,
queueMicrotask). - ఒకవేళ ఫ్రేమ్ సమయం అయితే, 60 fps లక్ష్యాన్ని చేరుకోవడానికి paint మరియు composite చేస్తుంది.
- మళ్ళీ ఇదే ప్రక్రియను పునరావృతం చేస్తుంది.
ఈ సైకిల్లో డెవలపర్లకు రెండు APIలు స్పష్టమైన అవకాశాలను ఇస్తాయి:
requestAnimationFrame– బ్రౌజర్ పెయింట్ చేయడానికి సరిగ్గా ముందు దీనిని పిలుస్తారు. యానిమేషన్ పనులకు ఇది సరైన చోటు, ఎందుకంటే ఈ callback ప్రస్తుత microtasks తర్వాత కానీ, తదుపరి ఫ్రేమ్ కంటే ముందే రన్ అవుతుంది.requestIdleCallback– బ్రౌజర్కు అత్యవసరమైన పనులు లేనప్పుడు దీనిని పిలుస్తారు. అనలిటిక్స్ లేదా డేటా ప్రీ-లోడింగ్ వంటి తక్కువ ప్రాధాన్యత కలిగిన పనులకు ఇది ఉపయోగపడుతుంది.
Pitfall: microtask starvation
బ్రౌజర్ రెండర్ చేయడానికి ముందు microtask క్యూను ఖాళీ చేస్తుంది కాబట్టి, ఒక పొడవైన promise chain వల్ల UI ఎప్పటికీ పెయింట్ కాకుండా పోవచ్చు. ఇక్కడ call stack బ్లాక్ అవ్వదు; పేజీ కేవలం రెండర్ దశకు చేరుకోదు, ఇది యూజర్కు స్క్రీన్ ఫ్రీజ్ అయినట్లు అనిపిస్తుంది.
Node యొక్క libuv-driven loop
Node.js తన లూప్ను libuvకి అప్పగిస్తుంది. ఇది ఒక C లైబ్రరీ, ఇది పనిని వేర్వేరు ఫేజ్లుగా విభజిస్తుంది, ప్రతి ఫేజ్కు దాని స్వంత క్యూ ఉంటుంది:
- Timers –
setTimeoutమరియుsetIntervalనుండి వచ్చే callbacks. - Pending callbacks – OS స్థాయిలో ఇప్పటికే పూర్తయిన deferred I/O callbacks.
- Poll – కొత్త I/O ఈవెంట్లను (file reads, network data) సేకరిస్తుంది.
- Check –
setImmediatecallbacksను రన్ చేస్తుంది. - Close callbacks – ఒక socket లేదా handle క్లోజ్ అయినప్పుడు రన్ అవుతుంది.
ఈ ఫేజ్ ఆర్డర్ వెలుపల రెండు నిర్మాణాలు ఉంటాయి:
process.nextTick– ప్రస్తుత ఆపరేషన్ పూర్తయిన వెంటనే, microtask క్యూ కంటే ముందు ఇది రన్ అవుతుంది.
Pitfall: I/O starvation
ఒక ఫంక్షన్ పదేపదే వేరే పనులకు అవకాశం ఇవ్వకుండా (yielding) process.nextTickను షెడ్యూల్ చేస్తూ ఉంటే, Node ఎప్పటికీ "next-tick" దశను దాటి ముందుకు వెళ్లదు. దీనివల్ల నెట్వర్క్ రిక్వెస్ట్లు, ఫైల్ రీడ్స్ మరియు టైమర్లు నిలిచిపోతాయి, ఇది సర్వర్-సైడ్ లేటెన్సీ స్పైక్లకు లేదా సర్వర్ పూర్తిగా హ్యాంగ్ అవ్వడానికి కారణమవుతుంది.
setImmediate vs. setTimeout ప్రాక్టికల్గా
రెండూ తదుపరి ఇటరేషన్ కోసం callbacksను షెడ్యూల్ చేస్తాయి, కానీ వాటి క్రమం అవి ఎక్కడ పిలవబడ్డాయి అనే దానిపై ఆధారపడి ఉంటుంది:
- Top-level code – వీటి క్రమం ఖచ్చితంగా ఉండదు; ఇది ప్రాసెస్ ఎంత వేగంగా స్టార్ట్ అవుతుంది అనే దానిపై ఆధారపడి ఉంటుంది.
- Inside an I/O callback – వీటి క్రమం ఖచ్చితంగా (deterministic) ఉంటుంది:
setImmediate,setTimeout(fn, 0)కంటే ముందే రన్ అవుతుంది. Poll phase పూర్తయిన తర్వాత, libuv మళ్ళీ zero-delay timeout కోసం Timers phaseలోకి వెళ్లేముందు, Check phase (ఇక్కడsetImmediateఉంటుంది)కి మారుతుంది.
రీడ్ పూర్తయిన వెంటనే ఒక రిసోర్స్ను క్లీన్ చేయడం వంటి ఖచ్చితమైన సీక్వెన్సింగ్ అవసరమైనప్పుడు ఈ సూక్ష్మతేడా చాలా ముఖ్యం.
క్లుప్తంగా ముఖ్యమైన తేడాలు
- Goal: బ్రౌజర్లు విజువల్ అప్డేట్లకు ప్రాధాన్యత ఇస్తాయి; Node I/O రెడీనెస్ (readiness) కు ప్రాధాన్యత ఇస్తుంది.
- Rendering hook:
requestAnimationFrame(బ్రౌజర్లో మాత్రమే). - Phase-specific hook:
setImmediate(Nodeలో మాత్రమే, Check phaseలో రన్ అవుతుంది). - High-priority queue:
process.nextTick(Nodeలో మాత్రమే, microtasks కంటే ముందు రన్ అవుతుంది). - Starvation risk: బ్రౌజర్లలో పొడవైన promise chains; Nodeలో అపరిమితమైన
process.nextTick.
తదుపరి ఏమి గమనించాలి?
మీరు రెండు వాతావరణాలలో (ఉదాహరణకు, isomorphic libraries) నడిచే కోడ్బేస్ను నిర్వహిస్తుంటే, ఈ క్రింది సందర్భాలను తనిఖీ చేయండి:
- ఈవెంట్ లూప్కు అవకాశం ఇవ్వకుండా చాలాే ఎక్కువ promisesలను చైన్ చేయడం. రెండరర్కు అవకాశం ఇవ్వడానికి
await new Promise(r => setTimeout(r, 0))ను ఇన్సర్ట్ చేయండి లేదా బ్రౌజర్లోrequestIdleCallbackను ఉపయోగించండి. - వాయిదా వేయగలిగే పనుల కోసం
process.nextTickను ఉపయోగించడం. మీకు “next-tick” అత్యవసరత అవసరం లేనప్పుడు,setImmediateలేదా సాధారణ promiseను ఉపయోగించడం ఉత్తమం. setTimeout(fn, 0)మరియుsetImmediateఒకేలా పనిచేస్తాయని అనుకోవడం. సీక్వెన్స్ ముఖ్యం అనుకున్నప్పుడు, I/O callbacks లోపల వాటి ఆర్డర్ను పరీక్షించండి.
సారాంశం
ఈవెంట్ లూప్ అనేది హోస్ట్-నిర్దిష్ట షెడ్యూలర్, ఇది యూనివర్సల్ JavaScript ఫీచర్ కాదు. బ్రౌజర్లు రెండరింగ్ను లూప్లో విలీనం చేస్తాయి; Node, I/Oని libuv ఫేజ్లుగా వేరు చేస్తుంది. ప్రాధాన్యత మెకానిజమ్లను—బ్రౌజర్లో microtasks, Nodeలో process.nextTick—తప్పుగా ఉపయోగించడం వల్ల, ఆ ఎన్విరాన్మెంట్ ఏ పని కోసం నిర్మించబడిందో, ఆ సిస్టమ్ భాగానికి అంతరాయం కలిగించవచ్చు. మీ async ప్యాటర్న్స్ను హోస్ట్ యొక్క లూప్ మోడల్కు అనుగుణంగా ఉంచుకోవడం ద్వారా, మీరు ఫ్రీజ్ అయిన పేజీలను మరియు బ్లాక్ అయిన సర్వర్లను నివారించవచ్చు.
