Skip to content

Chapter 18: Asynchronous JavaScript

18-1: Single-threaded পিঁপড়া

Single-threaded

পিঁপড়ারা যখন খাবার খুঁজে পায়, তারা খাবার থেকে তাদের বাসা পর্যন্ত একটা রাস্তা তৈরি করে। তাদের চলাফেরা হয় একজনের পর একজন, কেউ কাউকে অতিক্রম করে না। তারা একটা লাইনে চলে। আর এটাই হচ্ছে Single-threaded। মানে এক লাইনে চলে বা একসাথে একটা কাজ করে। একটা কাজ শেষ হলে তারপরের কাজটা করে। আশেপাশে ঠুসা দেয় না।

জাভাস্ক্রিপ্টে কোড যখন চলে, সে সোজা লাইনে ওপর থেকে নিচের দিকে যায়। যেমন ধর, আমরা এই তিনটা জিনিসকে আউটপুটে দেখতে চাই—

javascript
console.log(1);
console.log(2);
console.log(3);

Output: 1;
2;
3;

দেখবি, সিরিয়াল অনুসারে বা সিকোয়েন্সিয়াল জিনিসগুলা আউটপুটে দেখাচ্ছে। এমনকি মাঝখান থেকে একটা ফাংশনকে কল করলেও সিমিলার স্টাইল থাকবে। অর্থাৎ ওপর থেকে নিচের দিকে সিরিয়ালমতো যাবে।

javascript
console.log(1);
console.log(2);
doSomething();
console.log(4);
console.log(5);
console.log(6);

function doSomething() {
  console.log(3);
}

Output: 1;
2;
3;
4;
5;
6;

এখানেও সে কিন্তু সিকোয়েন্সিয়াল আউটপুট দেখিয়েছে। একদম ওপর থেকে নিচে। মাঝখানে ফাংশন কল করার দরকার হলে সেটাকে কল করে আউটপুট দেখিয়ে তার পরের লাইনে গেছে।

বাই নেচার জাভাস্ক্রিপ্ট single-threaded অর্থাৎ সে একটা সিরিয়ালওয়াইজ রাস্তা মেইনটেইন করে বা লাইন বাই লাইন কাজ করে। এইটা আপাত দৃষ্টিতে সত্যি হলেও পুরাপুরি সত্যি না; বরং এর ভিতরে আরও একটা কাহিনি আছে। সেই কাহিনি না হয় একটু পরে শুনবি।

মাঝেমধ্যে তুই শুনবি Synchronous নামে একটা কঠিন শব্দ। এইটার মানে হচ্ছে সমানতালে চলা বা তালে তাল মিলিয়ে চলে। যেমন, আমরা যদি প্যারেড করতে দেখি বিজয় দিবসে, তখন দেখি, আর্মি অফিসাররা সমানতালে পা ফেলে ফেলে যাচ্ছে। একটু আগেপরে বা একটু এলোপাথাড়িভাবে চলাফেরা করতেছে না। খুবই শৃঙ্খলভাবে চলতেছে।

Synchronous মানে সিরিয়ালমতো সিরিয়াল মেইনটেইন করে চলে। এক লাইনে চলে। এইটাকে আবার কেউ কেউ Single-threaded বা একটা লাইন বা সুতার ওপরে চলার সাথে তুলনা করে।

Practice:

  1. সিঙ্ক্রোনাস কোডের মধ্যে ফাংশন কল করার পর কী হবে?
  2. জাভাস্ক্রিপ্ট কি সিঙ্গেল থ্রেডেড?

18-2: Time ছাড়া Timeout

মাঝেমধ্যে পোলাপান ক্লাসে দুষ্টুমি করলে স্যার ক্লাস থেকে বের করে দেয়। শাস্তিস্বরূপ একজনকে বের করে দেয়ার পর বাকিরা কিন্তু বসে থাকে না; বরং ক্লাস কিন্তু চলতে থাকে। অনেক সময় স্যার কাউকে পাঁচ মিনিটের জন্য আবার কাউকে ৩০ মিনিটের জন্য রুম থেকে বের করে দেয়।

এই টাইম-আউট নিয়ে কথা বলার আগে একটু আগের কথায় ফেরত যাই।

একটু আগে বলছিলাম, জাভাস্ক্রিপ্ট সিরিয়াল অনুসারে ওপর থেকে নিচে লাইন বাই লাইন এক্সিকিউট হয়, যেটাকে আমরা Synchronous বা Single-threaded বলছি। সেটা সত্যি, তবে সেটার পাশাপাশি আরেকটা কিন্তু আছে। সেটা দেখার জন্য মাঝখানে একটা setTimeout ফাংশনকে কল করে দিবি। এই setTimeout ফাংশনকে কল করার সময় মিনিমাম একটা কলব্যাক ফাংশন তোকে দিতে হবে। নিচে আমি setTimeout-এর ভিতরের কলব্যাক ফাংশনের ভিতরে শুধু 3-কে কনসোল লগ করতেছি।

javascript
  console.log(1);

  console.log(2);
  setTimeout(()=>{
    console.log(3)
  });
  console.log(4);
  console.log(5);
  console.log(6);

Output: 1  2  4  5  6  --------- 3

দেখতেছস, 3 সবার শেষে আউটপুটে দেখাচ্ছে। এটা কেন হচ্ছে, সেটা একটু পর আলোচনা করতেছি। তবে তার আগে setTimeout-এর সেকেন্ড প্যারামিটার হিসেবে 4000 সেট করে দে।

javascript
  console.log(1);
  console.log(2);
  setTimeout(()=>{
    console.log(3)
  }, 4000);
  console.log(4);
  console.log(5);
  console.log(6);

Output: 1  2  3  4  5  6  --------- 3

এখন এটাকে রান করলে দেখবি, প্রথমে আউটপুটে 3 ছাড়া সবকিছুই দেখাচ্ছে, কিন্তু তারপর 4 সেকেন্ড পরে গিয়ে 3 দেখাবে। এর কারণ হচ্ছে, আমরা setTimeOut-এর সেকেন্ড প্যারামিটার হিসেবে 4000 মিলি সেকেন্ড (যা 4 সেকেন্ডের সমান) ওয়েট করতে বলেছি। তাই setTimeout-এর ভিতরে কলব্যাক ফাংশন চলার জন্য মিনিমাম 4 সেকেন্ড অপেক্ষা করবে।

যদি setTimeout-এ সেকেন্ড প্যারামিটার না থাকে, তাহলে সে 0 মিলিসেকেন্ড ধরে নেয় এবং সিরিয়ালমতো করার যে কাজগুলো আছে, সেগুলা আগে শেষ করে। তারপর setTimeout-এর ভিতরে ফাংশনকে কল করে।

আর যদি setTimeout সেকেন্ড প্যারামিটার থাকে, তাহলে বলে দেয়, setTimeout-এর সেকেন্ড প্যারামিটারের মান যত মিলিসেকেন্ড, তত মিলিসেকেন্ড পরে setTimeout-এর ভিতরের ফাংশনকে কল করবে।

অর্থাৎ জাভাস্ক্রিপ্ট স্বাভাবিকভাবে Synchronous হিসেবে চললেও এর মধ্যে মাঝে Asynchronous কিছু কাজ চলে আসতে পারে। Asynchronous মানে synchronous না। অর্থাৎ সিরিয়ালমতো একসাথে একটার পর একটা কাজ করবে না; বরং একসাথে একাধিক কাজ করবে। সিরিয়াল ব্রেক করবে। কাজ আগেপরে করবে। যেমনটা তুই দেখছস setTimeout করছে।

setTimeout-এর আরেকটা খালাতো ভাই আছে, সেটা হলো setInterval। এটা প্রায় কাছাকাছি কাজ করবে। এখানেও তুই দুইটা প্যারামিটার দিতে পারিস।

javascript
  setInterval(()=>{
    console.log('I M U')
  }, 1000)

Output: I M U  I M U  I M U   I M U>

এখন যদি এই জিনিসটা তুই তোর ব্রাউজারের console-এ গিয়ে দেখিস, দেখবি 1000 মিলিসেকেন্ড বা 1 সেকেন্ড পরপর I M U আউটপুট হিসেবে দেখাচ্ছে। আর একটার পর একটা আসতেই আছে। থামার কোনো নাম-গন্ধ নাই।

তবে কোন জিনিস কনসোল লগ করবে, তুই চাইলে সেটা চেইঞ্জ করতে পারবি। নিচের কোডের মতো জাস্ট ওপরে num নামে একটা ভেরিয়েবল ডিক্লেয়ার করছে, তারপর setInterval-এর ভিতরে num++ দিয়ে num ভেরিয়েবলের মান এক এক করে বাড়াতে পারবি।

javascript
  let num = 0;
  setInterval(()=>{
    num++;
    console.log(num);
  }, 1000)

Output: 1  2  3  4  5  6  7  8  9  10  --------->

এটাকে যদি তুই রান করিস, তোর console-এ দেখবি 1, 2, 3 এইভাবে প্রতিটা ভ্যালু 1000 মিলিসেকেন্ড বা 1 সেকেন্ড পরপর প্রিন্ট করছে এবং এই ফাংশন চলতেই থাকবে। এটা অনেকটা ঘড়ির কাঁটার মতো— চলতেই থাকবে, যতক্ষণ না তুই তাকে বন্ধ করিস।

এই setInterval কিন্তু এইটা আইডি রিটার্ন করে। তুই চাইলে সেটা একটা ভেরিয়েবলে রাখতে পারস। ধর, আমি intervalId নামক একটা ভেরিয়েবলে রাখলাম। তারপর setInterval-এর কলব্যাক ফাংশনের ভিতরে কোনো শর্তসাপেক্ষে clearInterval ফাংশনকে কল করে এই intervalId টা পাঠিয়ে দিবি। তাহলে যখন এই শর্ত পুরণ হবে, তখন setInterval স্টপ হয়ে যাবে।

javascript
  let num = 0;
  const intervalId = setInterval(() => {
    num++;
    console.log(num);

    // Stop the interval when num reaches 5
    if (num === 5) {
      clearInterval(intervalId);
    }
  }, 1000);

Output: 1  2  3  4  5

setTimeout vs setInterval

যদি তুই একবার কোনো কাজ delay দিয়ে চালাতে চাস (যেমন: ৫ সেকেন্ড পর নোটিফিকেশন দেখানো), তাইলে setTimeout ইউজ করবি। আর যদি তুই কোনো কাজ বারবার রিপিট করবি (যেমন: প্রতি ১ সেকেন্ডে ঘড়ির সময় দেখানো), তাহলে setInterval ইউজ করবি। এ ছাড়া setTimeout যেহেতু একবার চলবে, তাই একবার চলার পর আর বন্ধ করার কোনো দরকার নাই। তবে setTimeout চলার আগে বন্ধ করতে চাইলে বন্ধ করা যায়। অন্যদিকে setInterval চালু করিস, সেটা কিন্তু clearInterval না দিলে বারবার চলতেই থাকবে।

ফাইনাল কথা হচ্ছে— জাভাস্ক্রিপ্ট Single-threaded ভাষা, মানে একই সময়ে একটা কাজই করতে পারে। সে একসাথে একাধিক কাজ না করতে পারলেও একটা কাজের জন্য বসে থাকে না; বরং কিছু কাজ আছে, শুরু করে দিয়ে সেটা আরেকজনের ওপর দিয়ে রাখে, আর এইদিকে নিজের কাজ থাকলে সেগুলা চালাতে থাকে। এতে বাহির থেকে মনে হতে পারে, একাধিক কাজ একসাথে হ্যান্ডেল করতেছে। আসলে সে Asynchronous-এ কিছু কাজ হ্যান্ডেল করতেছে।

Practice:

  1. setTimeout() দিয়ে একটা ফাংশন তৈরি কর, যেখানে 3 সেকেন্ড পর "I wasted 3 seconds of my life by looking at screen and doing nothing" প্রিন্ট হবে।
  2. দুই সেকেন্ড পর পর একটা একটা করে সংখ্যা দেখাবে। 131 থেকে শুরু হবে এবং প্রতিবার দুই করে বাড়বে।
  3. দুই সেকেন্ড পরপর কনসোলে I am learning javascript লগ করবি এবং ৬ বার আউটপুট দেখানোর পর থেমে যাবে।
  4. setTimeout()-এর সেকেন্ড প্যারামিটার বাদ দিলে ডিফল্ট হিসেবে কত মাইক্রোসেকেন্ড ধরে নেয়?

18-3: জাভাস্ক্রিপ্টের স্যুপ Event loop

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-এ পাঠায়। আর ইভেন্ট কিউতেও যদি কাজ না থাকে, তখন আর কী করবে? চুপচাপ বসে বসে মুড়ি খায়।

একটা উদাহরণ দিয়ে কিছুটা বুঝার চেষ্টা কর।

javascript
  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:

  1. ইভেন্ট লুপ কীভাবে কাজ করে, বিস্তারিত ব্যাখ্যা কর।
  2. call stack আর callback queue-এর মধ্যে ডিফারেন্স কী।
  3. জাভাস্ক্রিপ্ট যদি সিঙ্গেল থ্রেডেড হয়, তাহলে asynchronous কাজগুলো কীভাবে হ্যান্ডেল করে?

18-4: রহস্যময়ী জাভাস্ক্রিপ্টের আসল রূপ

JavaScript True Form

দূর থেকে মানুষ চেনা যায় না। মানুষ চিনতে হয় তার পাশে থেকে, তার বন্ধু হয়ে, একসাথে অনেকটা পথ পাড়ি দিয়ে। সেই কাজটাই তুই শুরু করছস জাভাস্ক্রিপ্টের সাথে। হয়তো অনেকেই জাভাস্ক্রিপ্টকে অনেকভাবে বলবে, তুই নিজেও হয়তো কিছু একটা বলবি– দিনশেষে রহস্যময়ী জাভাস্ক্রিপ্ট তোকে আর সবাইকে মুগ্ধ করতেই থাকবে।

কেউ কেউ বলতে পারে– "Javascript uses abstraction to hide implementation details."। এতে abstraction বলতে কী বোঝায়? মানে হলো, ভিতরে ভিতরে কীভাবে কাজ করে, সেটা বাহির থেকে বুঝা যায় না। যেমন, তুই ATM মেশিন থেকে টাকা তুলছিস। তুই শুধু কার্ড দিস, পিন প্রবেশ করিস এবং টাকা তোলস। কিন্তু এর ভিতরে অনেক কাজ হচ্ছে। যেমন তোকে ভেরিফাই করা, একাউন্ট চেক করা ইত্যাদি। অথচ, তুই শুধু ফাংশনালিটি দেখছিস, বিস্তারিত জানস না।

Javascript-এর অনেক ফিচার নিজে থেকেই কাজ করে। যেমন, মেমোরি ম্যানেজমেন্ট এবং গারবেজ কালেকশন। Call Stack Management, Event Loop, Dynamic Typing, Type Conversion (Coercion), Error Propagation, আরও অনেক কিছু সামনে জানবি। Javascript নিজেই ভেজাইল্লা জিনিসগুলো সরিয়ে ফেলে। যাতে তুই গ্যাঞ্জাম পাকিয়ে না ফেলস।

Javascript এখন JIT (Just-In-Time) কম্পাইলড ল্যাঙ্গুয়েজ। একসময় interpreted লাঙ্গুয়েজ ছিল, যা লাইনে লাইনে রান হতো। JIT-এর মাধ্যমে কোড রান হওয়ার সময়ই কম্পাইল হয়ে যায়। ফলে, পারফরমেন্স অনেক বেড়ে গেছে।

Javascript হলো একটি multi-paradigm ল্যাঙ্গুয়েজ, যার অর্থ এটি procedural, Object Oriented Programming ( OOP ) এবং functional প্রোগ্রামিংয়ের জন্য ব্যবহার করা যায়। অর্থাৎ অনেকভাবে বড় বড় কোড অর্গানাইজ করা যায়।

Javascript একটি prototype-based ল্যাঙ্গুয়েজ, যেখানে object-গুলির মধ্যে ইনহেরিটেন্স ও কানেকশন থাকে। এ ছাড়া এটি dynamically typed, যার অর্থ অন্য প্রোগ্রামিং ল্যাঙ্গুয়েজের মতো ভেরিয়েবল টাইপ আগে থেকে বলে দিতে হয় না।

অনেকেই অনেকভাবে জাভাস্ক্রিপ্টকে তার মতো করে বুঝতে পারে। তবে কিছু কমন জিনিস হলো—

  • High abstraction
  • Dynamically typed
  • Prototype-based
  • Multi-paradigm
  • JIT compiled
  • Garbage collecting

এখন প্রশ্ন, এটি কীভাবে কাজ করে?

জাভাস্ক্রিপ্ট কোড ব্রাউজারে চালাতে গেলে ব্রাউজারের ভিতর আগে থেকেই জাভাস্ক্রিপ্টের কোড চালানোর একটা ইঞ্জিন সেট করা আছে। এইটা গাড়ির ইঞ্জিনের মতো না; বরং কোড চালানোর ইঞ্জিন। যার নাম V8 ইঞ্জিন। এইটা খুবই শক্তিশালী ইঞ্জিন। এটা Google-এর একটি ওপেন সোর্স ইঞ্জিন, যা জাভাস্ক্রিপ্ট এবং WebAssembly রান করে। এটি মূলত C++ দিয়ে তৈরি, যা দ্রুত কোড চালাতে সাহায্য করে।

V8 ইঞ্জিন প্রথমে জাভাস্ক্রিপ্ট কোডকে parse করে, অর্থাৎ কোডের স্ট্রাকচার বুঝে নেয়। তারপর প্রথমে Interpreter কোডটা পড়বে, তারপর JIT Compiler আসবে, আর কিছু অংশকে Machine Code-এ ট্রান্সফরম করে দিবে, যেটা পরবর্তীতে দ্রুত রান করবে এবং আউটপুট দেয়।

Practice:

  1. জাভাস্ক্রিপ্ট কীভাবে কোড রান করে?
  2. What is JavaScript?
  3. JIT compiled process বোঝানোর জন্য একটি ধাপের flowchart কোড লিখ।
  4. জাভাস্ক্রিপ্টে Memory management এবং garbage collection প্রসেস কীভাবে কাজ করে।

Released under the MIT License.