તમારા વેબ પેજને ચલાવતું JavaScript event loop, Node.js સર્વરને ચલાવતા event loop કરતા ખૂબ જ અલગ રીતે વર્તે છે, અને જો તમે સાવધ ન રહો તો આ તફાવત UI ને ફ્રીઝ કરી શકે છે અથવા I/O ને અવરોધી શકે છે. જે લોકો બંને વાતાવરણમાં ચાલતો async કોડ લખે છે, તેમના માટે આ બંને ક્યાં અલગ પડે છે તે જાણવું આવશ્યક છે.
આ તફાવત શા માટે મહત્વનો છે
event loop ECMAScript સ્પેસિફિકેશન દ્વારા વ્યાખ્યાયિત નથી; તે હોસ્ટ (host) માં રહેલું હોય છે. બ્રાઉઝરે ફ્રેમ્સ રેન્ડર કરતી વખતે પેજને પ્રતિભાવશીલ (responsive) રાખવું પડે છે, જ્યારે Node.js non-blocking I/O પર આધારિત છે. એક હોસ્ટમાં કામ કરતા પેટર્નને બીજામાં મિક્સ કરવાથી એવા બગ્સ પેદા થઈ શકે છે જે ફરીથી શોધવા મુશ્કેલ હોય: promises ની લાંબી સાંકળ બ્રાઉઝરના repaint ને અટકાવી શકે છે, જ્યારે અનચેક્ડ process.nextTick લૂપ Node ને તેના I/O તબક્કાઓ સુધી પહોંચતા રોકી શકે છે.
બ્રાઉઝરનું ટર્ન-બેઝ્ડ (turn-based) લૂપ
બ્રાઉઝરમાં, લૂપ એક સિંગલ સાયકલ ચલાવે છે જે task execution, microtask draining, અને rendering ને એકસાથે જોડે છે:
- એક macrotask ચલાવો (જેમ કે a click handler,
setTimeout, વગેરે). - તમામ microtasks ખાલી કરો (promises,
queueMicrotask). - જો કોઈ ફ્રેમ બાકી હોય, તો 60 fps ના લક્ષ્યને પહોંચવા માટે paint અને composite કરો.
- ફરીથી કરો.
બે APIs ડેવલપર્સને આ સાયકલના ચોક્કસ તબક્કાઓનો ઉપયોગ કરવાની સુવિધા આપે છે:
requestAnimationFrame– બ્રાઉઝર પેઇન્ટ કરે તે પહેલાં જ તેને કોલ કરવામાં આવે છે. એનિમેશનના કામ માટે આ યોગ્ય જગ્યા છે કારણ કે આ callback વર્તમાન microtasks પછી પરંતુ આગામી ફ્રેમ પહેલાં ચાલે છે.requestIdleCallback– જ્યારે બ્રાઉઝર પાસે કોઈ હાઈ-પ્રાયોરિટી કામ ન હોય ત્યારે તેને ઇનવોક કરવામાં આવે છે. તે એનાલિટિક્સ અથવા ડેટા પ્રી-લોડિંગ જેવા ઓછા પ્રભાવ ધરાવતા કાર્યો માટે ઉપયોગી છે.
જોખમ: microtask starvation
બ્રાઉઝર રેન્ડર કરતા પહેલા microtask ક્યુ (queue) ખાલી કરી દેતું હોવાથી, promises ની લાંબી સાંકળ UI ને ક્યારેય પેઇન્ટ થતા રોકી શકે છે. અહીં call stack બ્લોક નથી થતો; પેજ ફક્ત રેન્ડર સ્ટેપ સુધી પહોંચતું જ નથી, જે યુઝરને ફ્રીઝ જેવું લાગે છે.
Node નું libuv-driven લૂપ
Node.js તેનું લૂપ libuv ને સોંપે છે, જે એક C લાઈબ્રેરી છે જે કામને અલગ-અલગ તબક્કાઓમાં વહેંચે છે, જેમાં દરેકનો પોતાનો ક્યુ હોય છે:
- Timers –
setTimeoutઅનેsetIntervalમાંથી મળતા callbacks. - Pending callbacks – વિલંબિત (deferred) I/O callbacks જે OS લેવલ પર પહેલેથી જ પૂર્ણ થઈ ગયા છે.
- Poll – નવા I/O ઇવેન્ટ્સ મેળવે છે (file reads, network data).
- Check –
setImmediatecallbacks ચલાવે છે. - Close callbacks – જ્યારે સોકેટ અથવા હેન્ડલ બંધ થાય ત્યારે ફાયર થાય છે.
બે કન્સ્ટ્રક્ટ્સ આ ફેઝ ઓર્ડરની બહાર હોય છે:
process.nextTick– વર્તમાન ઓપરેશન પૂરું થયા પછી તરત જ, microtask ક્યુ કરતા પહેલા ચાલે છે.
જોખમ: I/O starvation
જો કોઈ ફંક્શન વળતો વળતો (repeatedly) yielding કર્યા વગર process.nextTick શેડ્યૂલ કરતું રહે, તો Node ક્યારેય "next-tick" સ્ટેપથી આગળ વધી શકતું નથી. નેટવર્ક રિક્વેસ્ટ, ફાઇલ રીડ્સ અને ટાઈમર્સ ખાલી બેસી રહે છે, જેના કારણે સર્વર-સાઇડ લેટન્સીમાં વધારો થાય છે અથવા સર્વર હેંગ થઈ જાય છે.
વ્યવહારમાં setImmediate વિરુદ્ધ setTimeout
બંને આગામી ઇટરેશન માટે callbacks શેડ્યૂલ કરે છે, પરંતુ તેમનો સાપેક્ષ ક્રમ તેઓ ક્યાં કોલ કરવામાં આવે છે તેના પર આધાર રાખે છે:
- Top-level code – ક્રમની ખાતરી આપી શકાતી નથી; તે પ્રોસેસ કેટલી ઝડપથી શરૂ થાય છે તેના પર આધાર રાખે છે.
- I/O callback ની અંદર – ક્રમ નિશ્ચિત (deterministic) છે:
setImmediate,setTimeout(fn, 0)પહેલા ચાલે છે. Poll ફેઝ પૂર્ણ થયા પછી, libuv ફરીથી ઝીરો-ડિલે ટાઈમઆઉટ માટે Timers ફેઝમાં પ્રવેશતા પહેલા Check ફેઝમાં (જ્યાંsetImmediateહોય છે) જાય છે.
જ્યારે તમે ચોક્કસ સિક્વન્સિંગ પર આધાર રાખતા હોવ, જેમ કે રીડ પૂર્ણ થયા પછી તરત જ રિસોર્સ ક્લીનઅપ કરવું, ત્યારે આ સૂક્ષ્મ તફાવત મહત્વનો બને છે.
એક નજરમાં મુખ્ય તફાવતો
- લક્ષ્ય: બ્રાઉઝર વિઝ્યુઅલ અપડેટ્સને પ્રાધાન્ય આપે છે; Node I/O રેડીનેસને પ્રાધાન્ય આપે છે.
- Rendering hook:
requestAnimationFrame(માત્ર બ્રાઉઝર માટે). - Phase-specific hook:
setImmediate(માત્ર Node માટે, Check ફેઝમાં ફાયર થાય છે). - High-priority queue:
process.nextTick(માત્ર Node માટે, microtasks પહેલા ચાલે છે). - Starvation જોખમ: બ્રાઉઝરમાં લાંબી promise સાંકળો; Node માં અનબાઉન્ડેડ
process.nextTick.
આગળ શું ધ્યાન રાખવું
જો તમે એવા કોડબેઝનું સંચાલન કરો છો જે બંને વાતાવરણમાં ચાલે છે (દા.ત., isomorphic libraries), તો એવી દરેક જગ્યા તપાસો જ્યાં તમે:
- event loop ને yielding કર્યા વગર ઘણી બધી promises ચેઈન કરો છો. રેન્ડરરને તક આપવા માટે
await new Promise(r => setTimeout(r, 0))ઉમેરો અથવા બ્રાઉઝરમાંrequestIdleCallbackનો ઉપયોગ કરો. - એવા કામ માટે
process.nextTickનો ઉપયોગ કરો જે વિલંબિત કરી શકાય તેમ હોય. જ્યારે તમને "next-tick" તાકીદની જરૂર ન હોય ત્યારેsetImmediateઅથવા રેગ્યુલર promise ને પસંદ કરો. - એવું માની લો કે
setTimeout(fn, 0)અનેsetImmediateએકબીજાના બદલે વાપરી શકાય તેવા છે. જો સિક્વન્સ મહત્વની હોય, તો I/O callbacks ની અંદરના ઓર્ડરનું ટેસ્ટિંગ કરો.
મુખ્ય તારણ
ઇવેન્ટ લૂપ (event loop) એ હોસ્ટ-વિશિષ્ટ શેડ્યુલર છે, તે કોઈ યુનિવર્સલ JavaScript ફીચર નથી. બ્રાઉઝર્સ રેન્ડરિંગને લૂપમાં વણી લે છે; Node I/O ને libuv ફેઝમાં અલગ કરે છે. પ્રાયોરિટી મિકેનિઝમ્સનો દુરુપયોગ—બ્રાઉઝરમાં microtasks, Node માં process.nextTick—સિસ્ટમના તે ભાગને અવરોધી શકે છે જેના માટે દરેક એન્વાયરમેન્ટ બનાવવામાં આવ્યું છે. તમારા async પેટર્નને હોસ્ટના લૂપ મોડેલ સાથે સુસંગત બનાવો, અને તમે ફ્રોઝન પેજ અને બ્લોક થયેલા સર્વર્સ બંનેથી બચી શકશો.
