জাভাস্ক্রিপ্টের স্যুপ Event loop
জাভাস্ক্রিপ্ট সিঙ্গেল-থ্রেডেড মানে একই সময়ে একটাই কাজ করতে পারে। কিন্তু অ্যাসিঙ্ক্রোনাস কাজ সামলাতে এটা Event Loop ব্যবহার করে, যেটা অনেকটা ম্যাজিকের মতো কাজ করে!
ইভেন্ট লুপ রিলেটেড কয়েকটা জিনিস আছে।
Call Stack
এখানে সব সিঙ্ক্রোনাস কাজ রাখা হয়। যখন কোনো function কল হয়, সেটা call stack-এ ঢুকে। রান হলে stack থেকে বের হয়। যদি সেই ফাংশনের ভিতর থেকে আরেকটা ফাংশনকে কল করা হয়, তাহলে সেটা আগের ফাংশনের ওপরে কল স্ট্যাকে জমা হবে। সেটার ভিতর থেকেও যদি অন্য আরেকটা ফাংশনকে কল করা হয়, সেটা তার উপরে জমা হবে। এইভাবে একটার ওপর আরেকটা জমা হতে থাকবে এবং সবার ওপরে যে থাকবে, সেটার কাজ সবার আগে শেষ হবে।
অর্থাৎ কী কী কাজ চলতেছে, সেটার একটা ডাইনামিক লিস্ট। কাজ আসলে স্তূপ জমতে থাকে। আবার একটার পর একটা কাজ শেষ হতে হতে স্তূপ খালি হতে থাকে। এই কনসেপ্টকে বলে LIFO (Last In, First Out) হয়। সবার শেষে যে কাজ যোগ হবে, সেটা সবার আগে শেষ হবে।
Synchronous কাজ সরাসরি call stack-এ রান হয়।
Web APIs: এখানে ব্রাউজারের কিছু ফিচার থাকে। যেমন, setTimeout, fetch ইত্যাদি। এগুলো Call Stack-এর বাইরে চলে। কারণ, এই কাজগুলোর জন্য বাইরের জিনিসের ওপরে নির্ভর করতে হয়, অপেক্ষা করতে হয়। তাই এদের আলাদা রাখতে হয়। যাতে এগুলার যে অন্য call stack-এ যত কাজ আছে, সেগুলা আটকে বসে না থাকে।
জাভাস্ক্রিপ্ট non-blocking আচরণ ধরে রাখে। অর্থাৎ যে কাজটা আসতে দেরি হবে, তার জন্য সে বসে থাকতে চায় না।
Callback Queue
কিউ (queue) বলতে বুঝায় অপেক্ষার লাইন বা সিরিয়াল ধরে সুযোগের জন্য অপেক্ষা করে। Event loop সবসময় call stack দেখে। যদি call stack ফাঁকা থাকে, তখন event queue-এর দিকে তাকায়। সেখানে যদি কোনো কাজ শেষে রেডি হয়ে বসে থাকে, তাহলে সেটাকে কলস্ট্যাকে পাঠায়। এরপর কলস্ট্যাকে queue-এর কাজ এক্সিকিউট হয়।
যদিও ভিতরে ভিতরে দুইটা কিউ মেইনটেইন করে। একটা বড় বড় কাজ হ্যান্ডেল করার জন্য, আরেকটা ছোট ছোট কাজ হ্যান্ডেল করার জন্য। তারপরেও সিম্পলভাবে চিন্তা করতে পারস, ভিতরে ভিতরে Queue মেইনটেইন করে।
Event queue একটা FIFO (First In, First Out) মডেলে কাজ করে। মানে, যেটা প্রথমে queue-তে এসেছে, সেটা আগে প্রসেস হয়।
Queue শুধু Asynchronous কাজের জন্য রাখা হয়।
Event Loop:
একটা লুপ কন্টিনিউয়াসলি চলতে থাকে, আর বারবার চেক করে, Call Stack খালি আছে কি না। যদি খালি না থাকে, তাহলে সে কলস্ট্যাকের কাজ করে। আর যদি কলস্ট্যাক খালি থাকে, তাহলে Callback Queue থেকে কাজ নিয়ে Call Stack-এ পাঠায়। আর ইভেন্ট কিউতেও যদি কাজ না থাকে, তখন আর কী করবে? চুপচাপ বসে বসে মুড়ি খায়।
একটা উদাহরণ দিয়ে কিছুটা বুঝার চেষ্টা কর।
console.log("Start");
setTimeout(() => {
console.log("Timeout 1");
}, 5000);
setTimeout(() => {
console.log("Timeout 2");
}, 500);
console.log("End");
Output:
Start
End
Timeout 2
Timeout 1আউটপুট কেন এমন হলো, সেটার লজিক হচ্ছে—
প্রথমেই কোড দেখছে, console.log("Start") লেখা আছে। এইটা একটা সিনক্রোনাস কাজ। তাই এইটা সরাসরি call stack-এ যায়। রান হয়ে stack থেকে বের হয়ে যায় এবং আউটপুট দেখায় Start।
তারপর আছে একটা setTimeout-এর কাজ। এইটা asynchronous কাজ। তাই এইটা Web API-তে যায়। Timer শুরু হয় (5000 মিলি সেকেন্ড বা 5 second)। তখন call stack ফাঁকা হলেও 5 সেকেন্ড বা 5000 মিলিসেকেন্ড শেষ হয়নি, তাই এইটা দেখাবে না।
তারপর আরেকটা setTimeout-এর কাজ। এইটাও asynchronous কাজ। তাই এইটাও Web API-তে যায়। Timer শুরু হয় (500 মিলিসেকেন্ড বা 0.5 second)। তখনো এইটা শেষ হয় নাই, তাই call stack ফাঁকা হলেও আউটপুট দেখাবে না।
এরপর আসবে console.log("End"), এইটা একটা synchronous কাজ। তাই সরাসরি call stack-এ যায়। রান হয়ে আউটপুট দেখিয়ে stack থেকে বের হয়ে যায়। কল স্ট্যাক ফাঁকা হয়ে যায়।
তারপর ওই যে Web API-তে 0.5 second পরের একটা setTimeout ছিল, সেটা পরে দিলেও সেটার কাজ ছিল 0.5 সেকেন্ড পরে। তাই সেকেন্ড কাজটা আগে শেষ হয়ে। তারপর সেটা callback queue-তে যায়। তখন কলস্ট্যাক ফাঁকা আছে দেখে সেটার আউটপুট আগে দেখায়। তারপর কিছুক্ষণ পরে 5 second পরে প্রথম setTimeout-এর কাজ শেষ হয়ে সেটা callback queue-তে যায়। তখন কলস্ট্যাক ফাঁকা থাকায় সেটা কলস্ট্যাকে যায় এবং setTimeout 1-এর কাজ stack-এ রান হয়। আর আউটপুট Timeout 1 আসে।
এই Event Loop-ই জাভাস্ক্রিপ্টকে স্মার্ট বানায়। কারণ, এটা সিঙ্গেল থ্রেডেড হলেও মনে হয় যেন একসাথে অনেক কাজ করছে!
Practice:
- ইভেন্ট লুপ কীভাবে কাজ করে, বিস্তারিত ব্যাখ্যা কর।
- call stack আর callback queue-এর মধ্যে ডিফারেন্স কী।
- জাভাস্ক্রিপ্ট যদি সিঙ্গেল থ্রেডেড হয়, তাহলে asynchronous কাজগুলো কীভাবে হ্যান্ডেল করে?
