Asynchronous JavaScript - callbacks, promises and async-await

জাভাস্ক্রিপ্টকে বলা হয় সিংগেল থ্রেড প্রোগ্রামিং ল্যাঙ্গুয়েজ কারন এটি একেবারে একটি কাজই করতে পারে একসাথে অনেক গুলো কাজ করতে পারে না। যদি একটি উদাহরন দিয়ে বলি তাহলে,

ধরুন একটি রেস্তোরা আছে সেখানে একজন ওয়েটার আছে এবং ২ জন কাস্টমার আছে ২ টি টেবিল এ এখন ওয়েটার একটা টেবিল থেকে অর্ডার নিয়ে কিচেন এ যাবে তার রান্না শেষ করে কাস্টমার এর কাছে খাবার পৌছে অর্ডার শেষ করে তারপর ২য় জন এর কাছে যাবে অর্ডার নিতে। অর্থাৎ এখানে ওয়েটার একটা কাজ পেলে তা শেষ করা পর্যন্ত অন্য কোনো কাজ করতে পারে না। এখানে ওয়েটের কিচেনে ব্লক হয়ে থাবে।

জাভাস্ক্রিপ্ট ও বাই ডিফল্ট এভাবে কাজ করে থাকে। সে একসাথে একটি কাজ এ করতে পারে তাই তার এই ব্যবহারকে সিঙ্ক্রোনাস ব্লকিং ব্যবহারও বলে। চলুন একটা উদাহরন দিয়ে দেখে আসি।

const processOrder = (customer) => { console.log(`Process order for customer 1`); for (let i = 0; i < 1000000000; i++) { const arr = []; const random = Math.random() * i; arr.push(random); } console.log(`order processed for customer 1`); }; console.log(`take order for customer 1`); processOrder(); console.log(`completed order for customer 1`);

উপরের কোডে লক্ষ্য করুন যে processOrder() এ একটি লুপ দেওয়া হয়েছে অনেক বড়। এই কাজ টা করতে একটু সময় নিবে এখন যখন কোড গুলো লাইন বাই লাইন যাবে প্রথমে ফাংশন তো যেভাবে আছে থাকবে কারন তাকে কল করা না হলে ত কিছু করার নাই তার পর দেখুন ১০ নাম্বার লাইনে প্রথম কন্সোল করবে তার পর ফাংশন টা কল করা হলো এখন সে ফাংশন বডিতে যাবে এখানে একটা বড় নাম্বারকে গুন করে অ্যারে পুশ করার কাজটা অনেক সময় সাপেক্ষ এখানে সে ব্লক হয়ে থাকবে যতক্ষন না এটা শেষ হবে ততক্ষন যে লাস্ট কন্সোল এ যাবে না, এমন কি এই সময়টাই ইউজার কোথায় ও কোনো ক্লিক কিছুই করতে পারবে না একপ্রকার ব্রাউজার হ্যাং করে যাবে। কিভাবে জাভাস্ক্রিপ্ট কোড গুলো ব্রাউজারে রান হয় কল স্টেক এক্সিকিউশন কন্টেক্স সমপর্কে জানতে আমার লেখা আর্টিকেল টি পরে আসতে পারেন।

এখানে আমাদের সমস্যা হচ্ছে for loop এ হেভি কাজটি, এখন এই জাভাস্ক্রিপ্ট এর এই ব্লকিং ব্যবহার থেকে বের হয়ে আসার জন্য ব্যবহার করতে পারি অ্যাসিঙ্ক্রোনাস নন-ব্লকিং ওয়ে। মজার বেপার হলো আমার জাভাস্ক্রিপ্ট কিছু বিল্ট ইন ফাংশন আছে তাদের ব্যবহার করে এই বেপার টা বুঝতে পারি চলুন আমাদের কোডে কিছুটা পরিবর্তন করে অ্যাসিঙ্ক্রোনাস করে নিই।

const processOrder = (customer) => { console.log(`Process order for customer 1`); setTimeout(() => { console.log(`order processed for customer 1`); }, 2000); }; console.log(`take order for customer 1`); processOrder(); console.log(`completed order for customer 1`);

এখানে শুধু while loop এর পরিবর্তে ব্যবহার করা হয়েছে setTimeout() । এটি এমন একটি ফাংশন যা প্রথম প্যারামিটারে একটি কলব্যাক ফাংশন নেই এবং সেকেন্ড প্যারামিটার এ কত মিলি সেকেন্ড পর এই কলব্যাক কে কল করবে তা নেয়। আরো ভালো করে বোঝার সুবিধার জন্য এখানে setTimeout() এ আমরা console.log(order processed for customer 1); এটিকে দিয়ে দিলাম, এখানে বুঝানো হলো যে ২ সেকেন্ড পর প্রথম কাস্টমারের খাবার processed । এখানে ২ সেকেন্ড ওয়েট করবে কিন্তু এখানে একটু পরিবর্তন আছে নিচে আউটপুট টি লক্ষ্য করুন।

// output take order for customer 1Process order for customer 1completed order for customer 1order processed for customer 1

উপরে খেয়াল করুন খাবার processed হবার আগেই কমপ্লিট করে দিছে এখানেও অ্যাসিঙ্ক্রোনাস একটু সমস্যা আছে এটাকে আমার হ্যান্ডেল করব এই বেপারে একটু পরেই আসছি। কিন্তু আমাদের সমস্যা সমাধান হলো কিনা তা খেয়াল করুন। এখানে যে ২ সেকেন্ড ওয়েট করতে হলে processed হতে তার জন্য কি আমাদের অপেক্ষা করতে হয়েছে চলুন সবার শেষে আরেকটা কন্সোল করে আর একটি বেপার টা ইজি করি।

const processOrder = (customer) => { console.log(`Process order for customer 1`); setTimeout(() => { console.log(`order processed for customer 1`); }, 2000); }; console.log(`take order for customer 1`); processOrder(); console.log(`completed order for customer 1`); console.log(`Take a new order man 🍔`);

এইবার এটা কে রান করে আউটপুট দেখি তাহলে আমাদের যে ব্লকিং ব্যবহার ছিলো অইটে যে আর নাই তা ভালো করে বুঝতে পারবেন।

// output take order for customer 1 Process order for customer 1 completed order for customer 1 Take a new order man 🍔 - 🆕 order processed for customer 1

এখানে দেখুন এখানে কিন্তু processed এর আগে আরেকটি নিউ অর্ডার নিয়েছে ওয়েটার কিন্তু এখানে আরেকটি প্রব্লেম উদয় হলো তা হলো কখন কখন processed হচ্ছে কখন কমপ্লিট হচ্ছে তার কোনো ঠিক নাই মানে এই ফ্লো টা আমাদের হাতে নাই।

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

এখন প্রশ্ন আসতে পারে যে ভাই আপনি তো অইখানে ২ সেকেন্ড দিয়েছেন 😲 সেখানে ১ সেকেন্ড দেন তো। আপনি সেখানে ০ সেকেন্ড দিয়ে চেষ্টা করে দেখবেন এটা সবার শেষে এ আসবে। এটা ওয়েব অ্যাপিআই, কলব্যাক কিউ ঘুরে ইভেন্ট লুপ থেকে সবার শেষে কল স্টেক এ আসবে। এখন এই যে, যে যেভাবে পারছে দিয়ে দিচ্ছে বেপারটা কেমন না, আমার কাস্টমার খাবার পেলো না প্রসেসই হলো না কিন্তু আমি বলছি অর্ডার কমপ্লিট একটু পরে বলছি খাবার প্রসেস তাইলে তো কাস্টামার থাকবে? এখন বলবেন, ভাই কি বলেন এই সব। অকে মাথা ঠান্ডা রিয়েল লাইফে কি হয় বলছি। যখন আমার কোনো ডাটা গেট করি সার্ভার থেকে, কোনো তথ্য কোয়েরি করে আনি ডাটাবেজ থেকে তখন একটু হলে সময় নেই অর্থাৎ অ্যাসিঙ্ক্রোনাস কাজ এবং সব সময়ই এই ডাটা উপর বেস করে আমাদের পরবর্তী কাজ টা হয়ে থাকে এই ক্ষেত্রে যদি আমরা ডাটা ঠিকঠাক আসছে কিনা তা নিশ্চিত না হয়ে পরবর্তী স্টেপ এ যাই তখন জাভাস্ক্রিপ্ট এর ভাষায় , এরর - রেফারেন্স এরর - আন্ডিফাইন্ড অনেক কিছু পেতে পারি। আমাদের সিস্টেম কাজ করবে না। তাই কোড করার সময় যেমন আমাদেরকে অ্যাসিঙ্ক্রোনাস ভাবে কোড লেখতে চেষ্টা করতে হবে ঠিক তেমনি তাকে প্রপার ভাবে হ্যান্ডেল করতে হবে। এখন এই অ্যাসিঙ্ক্রোনাস কোড গুলোকে তিন ভাবে মূলত দুই ভাবে হ্যান্ডেল করা যায়,

  • কলব্যাক ফাংশন ( callback )
  • প্রমিস ( promise .then block )
  • অ্যাসিঙ্ক-অ্যাওয়েট ( async await )

চলুন কিভাবে এই তিন ভাবে অ্যাসিঙ্ক্রোনাস কোড হ্যান্ডেল করতে হয় তা দেখবো এবং কলব্যাক, প্রমিস থেকে ও কেনো অ্যাসিঙ্ক-অ্যাওয়েট ভালো তা জানার চেষ্টা করবো।

কলব্যাক ফাংশন ( callback )

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

const takeOrder = (customer) => { console.log(`take a order for ${customer}`); }; const processOrder = (customer) => { console.log(`Processing order for ${customer}`); setTimeout(() => { console.log(`Cooking completed for ${customer}`); console.log(`Cooking processed form ${customer}`); }, 2000); }; const completeOrder = (customer) => { console.log(`Order completed for ${customer}`); };

এখন তেমন কিছুই করা হয় নি প্রত্যেকটি কাজ কে একটি ফাংশন ডেফিনেশনে নেওয়া হলো এবং প্যারামিটার আকারে কাস্টমারের নাম দেওয়া হলো একটু ডাইনামিক করা হলো। এখন যদি এভাবে কল করি

takeOrder('Jhon Dou'); processOrder('Jhon Dou'); completeOrder('Jhon Dou'); /* take a order for Jhon Dou ✔ Processing order for Jhon Dou ✔ Order completed for Jhon Dou ❌ Cooking completed for Jhon Dou ❌ Cooking processed form Jhon Dou ❌ */

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

const takeOrder = (customer, callback) => { console.log(`take a order for ${customer}`); callback(customer); }; const processOrder = (customer, callback) => { console.log(`Processing order for ${customer}`); setTimeout(() => { console.log(`Cooking completed for ${customer}`); console.log(`Cooking processed form ${customer}`); callback(customer); }, 2000); }; const completeOrder = (customer) => { console.log(`Order completed for ${customer}`); }; takeOrder('Jhon Dou', (customer) => { processOrder(customer, (customer) => { completeOrder(customer); }); });

চলুন এখানে কি হচ্ছে তা বলি। প্রথমে তো takeOrder() কল হবে এখন আমাদের কাজ হচ্ছে takeOrder() এর পরেই processOrder() অর্ডার হবে, তাহলে takeOrder() এর ভিতরে অর্ডার নেবার পর একটা ফাংশন কল হবে রিমেম্ভার takeOrder() কমপ্লিট হবার পর তাই একটা কলব্যাক ফাংশন নিলাম এবং সেখানে কাস্টমারের নাম কলব্যাক এ দিয়ে দিলাম।

const takeOrder = (customer, callback) => { console.log(`take a order for ${customer}`); callback(customer); };

এখন হচ্ছে কল করার পালা চলুন অর্ডার নেবার জন্য takeOrder() কে কল করি।

takeOrder('Jhon Dou');

খেয়াল করুন এখানে আমার প্যারামিটারে নাম দিয়ে কল করে দিলাম এখন এর পরে যে আরেকটি প্যারামিটার আছে অর্ডার পর কি করবে তার জন্য তা তো দেই নি এখন আমরা জানি তার পর হবে processOrder() তাকে আমরা কলব্যাক এ পাঠাবো । এখাবে আমার কল ব্যাক এ একটি অ্যারো ফাংশন দিচ্ছি সেখানে আমরা processOrder() কে কল করে দিচ্ছি।

takeOrder('Jhon Dou', () => { processOrder(); });

অকে কল হয়ে গেলো কিন্তু processOrder() ফাংশন তো একটি কাস্টমারের নাম চায় তাই আমার অ্যারো ফাংশন এর প্যারামিটারে নামটা দিয়ে দেব সেটা আবার আমরা processOrder() এ পাস করব এভাবে।

takeOrder('Jhon Dou', (customer) => { processOrder(customer); });

আর অ্যারো ফাংশনের customer ভ্যালু কিন্তু আমরা takeOrder() এ কল করার সময় সে callback(customer) করছিলাম সেখানে customer পাস করেছিলাম এখান থেকে কাস্টমারের নামটা পাচ্ছে।

এবার processOrder() কল হচ্ছে এখন আমাদের কাজ হচ্ছে setTimeout() শেষ হলে তারপর completeOrder() কে কল করা তাই আগের মতো আরেকটি কল ব্যাক নিতে হবে এবং প্রসেস শেষ হবার পর তাকে কল করে দিতে হবে।

const processOrder = (customer, callback) => { console.log(`Processing order for ${customer}`); setTimeout(() => { console.log(`Cooking completed for ${customer}`); console.log(`Cooking processed form ${customer}`); callback(customer); }, 2000); };

এখন যে কল ব্যাক টা এখানে দিচ্ছি সেটা আবার completeOrder() কে কল করবে তার ডেফিনেশন টা আমার কন্টোল করবো এভাবে,

takeOrder('Jhon Dou', (customer) => { processOrder(customer, (customer) =>{ completeOrder() }); });` ``

এখন কমপ্লিট অর্ডার শুধু কাস্টমারের নাম চায় তা তো আমরা processOrder() থেকে পাচ্ছি সেটাকে আমরা কল ব্যাক পেয়েছিলাম সেটাকে কে আমরা completeOrder() এ পাস করে দিবো যাস্ট।

takeOrder('Jhon Dou', (customer) => { processOrder(customer, (customer) =>{ completeOrder('customer') }); });` ``

এবার বুঝতে পারছেন কিভাবে একটার পর একটা কল করে করে কাজটা হচ্ছে একেই বলে কলব্যাক, যে একটা কমপ্লিট হলো তো তার পর পরবর্তী কাজটি কে কল করে দাও আর এটা আমাদের পরবর্তী কোড কে আটকাবে না। এটি অ্যাসিঙ্ক্রোনাস ভাবে কাজ করবে নন-ব্লকিং ভাবে।

const takeOrder = (customer, callback) => { console.log(`take a order for ${customer}`); callback(customer); }; const processOrder = (customer, callback) => { console.log(`Processing order for ${customer}`); setTimeout(() => { console.log(`Cooking completed for ${customer}`); console.log(`Cooking processed form ${customer}`); callback(customer); }, 2000); }; const completeOrder = (customer) => { console.log(`Order completed for ${customer}`); }; takeOrder('Jhon Dou', (customer) => { processOrder(customer, (customer) => { completeOrder(customer); }); }); console.log('take new order man 🍔'); /* take a order for Jhon Dou ✔ Processing order for Jhon Dou ✔ take new order man 🍔 - একটি অর্ডার প্রসেস হবার টাইম এ আরেকটি নিতেই পারে ✔ Cooking completed for Jhon Dou - তারপর ১ম টা কমপ্লিট ✔ Cooking processed form Jhon Dou - তারপর প্রসেস ✔ Order completed for Jhon Dou - তারপর অর্ডার কমপ্লিট ✔ */

অর্থাৎ আমাদের ফ্লো তে কোনো সমস্যা হচ্ছে না এবং আমাদের অন্যান্য কোড গুলোকে রান হতে দিচ্ছে কোথাও ব্লক থাকছে না।

Problem of callback

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

takeOrder('Jhon Dou', (customer) => { processOrder(customer, (customer) => { // do another stuff completeOrder(customer, (customer) => { completeOrder2(customer, (customer) => { completeOrder3(customer, (customer) => { // can anothe code completeOrder4(customer, (customer) => { completeOrder5(customer, (customer) => { // can other function anything inside a arrow function completeOrder6(customer, (customer) => { completeOrder7(customer, (customer) => { completeOrder8(customer); }); }); }); }); }); }); }); }); });

যেটাকে বলা হতো কলব্যাক হেল তাই শুরুর দিকে জাভাস্ক্রিপ্ট কে অনেকে আগলি ল্যাঙ্গুয়েজ বলা হতো। এখন জাভাস্ক্রিপ্ট অনেক ইভ্ল হয়েছে অনেককিছু একটি সিন্টেকটিক্স ওয়েতে এসেছে। আর এই হেল থেকে বাঁচার জন্য এসেছে প্রমিস (Promise) । চলুন দেখে আসি এ প্রমিস কিভাবে কাজ করে আর কিভাবে ডেভোলপার লাইফ ইজি করেছে।

প্রমিস ( promise )

প্রমিস তার কাজ অনেকটা তার নামের মতোই। যদি কোনো কাজে সাকসেস হয় তাহলেPromise.resolve() রান হবে আর যদি ফেইল হয় তাহলে Promise.rejected() রান হবে।

চলুন একটা প্রমিস তৈরি করা যাক,

const promise = new Promise((resolve, reject) => { // "Producing Code" (May take some time) resolve(); // when successful reject(); // when error });

এখানে new Promise() দিয়ে একটি প্রমিস বানানো হলো, এখানে প্যারামিটারে আমরা যেকোনো ফাংশন নিতে পারি। আমরা অ্যারো ফাংশন নিলাম এবং এই ফাংশনের প্যারামিটারে resolve, reject মানে দুটি ফাংশন থাকবে আপনি অন্য নাম দিতে পারেন এটা হলো কনভেনশন। তার পর কন্ডিশন অনুযায়ী সত্য হলে reslove() কে আর মিথ্যা হলে reject() কে কল করবে। চলুন এই সিন্টেক্স কাজে লাগিয়ে একটা রিয়েল উদাহরন বানাই।

const hasMeeting = false; const meeting = new Promise((reslove, reject) => { if (hasMeeting) { const meetingDetails = { name: 'Technical meeting', location: 'Google meet', time: '10:00PM', }; reslove(meetingDetails); } else { const error = new Error('No meeting found'); reject(error); } });

এবার আসি এই প্রমিসকে কিভাবে ব্যবহার করব? চলুন দেখা যাক

promise .then((res) => { // if reslove // get reslove data in res }) .catch((err) => { // if reject // get reject error data in err });

অর্থাৎ যদি আমাদের প্রমিস থেকে ডাটা resolve হয় তাহলে তার পর কি হবে তার ডাটা হ্যান্ডেল করার জন্য .then(()=>{}) এখানে অ্যারো ফাংশন এ হ্যান্ডেল করতে পারব আর যদি reject হয় তাহলে তার পর কি হবে তার ডাটা হ্যান্ডেল করার জন্য .catch(()=>{}) এখানে অ্যারো ফাংশন এ হ্যান্ডেল করতে পারব।

চলুম এই সিন্টেক্স কাজে লাগিয়ে আমাদের তৈরি করা meeting প্রমিসটাকে হ্যান্ডেল করি।

const hasMeeting = false; const meeting = new Promise((reslove, reject) => { if (hasMeeting) { const meetingDetails = { name: 'Technical meeting', location: 'Google meet', time: '10:00PM', }; reslove(meetingDetails); } else { const error = new Error('No meeting found'); reject(error); } }); meeting .then((res) => { console.log(JSON.stringify(res)); }) .catch((err) => { console.log(err.message); }); //output - No meeting found

দেখুন কোনো মিটিং নাই false তাই reject কল হচ্ছে।

দেখুন এখানে যদি অনেক গুলো স্টেপ থাকত তাহলে আমরা .then দিয়ে ব্যবহার করতে পারতাম।

meeting .then((res) => { console.log(JSON.stringify(res)); }) .then(() => { console.log(`do any stuff`); }) .then(() => { console.log(`do any stuff`); }) .catch((err) => { console.log(err.message); });

এটা অনেকটাই হিউম্যান ফ্রেন্ডলি আমারা সহজে পরতে পারছি। আসলে প্রমিস এর আরো অনেক ইমপ্লিমেন্টেশন আছে বিভিন্ন থার্ড পার্টি প্যাকেজ যেমন Axios কিন্তু প্রমিস রিটার্ন করে তারপর কিন্তু আমরা সেখানে .then & .catch দিয়ে ডাটা গুলো পেয়ে থাকি।

আমরা আমাদের উদাহরনে যাস্ট একটা অব্জেক্ট কে স্ট্রিংগিফাই করেছি কিন্তু রিয়েল লাইফে এখানে যে কাজ টা হবে একটু সময় নিবে অ্যাসিঙ্ক্রোনাস হবে আর হে এখানে যে প্রমিস অ্যাসিঙ্ক্রোনাস ভাবে কাজ করছে তার জন্য সবার শেষে একটা কন্সোল লগ করে দেখতে পারি।

meeting .then((res) => { console.log(JSON.stringify(res)); }) .catch((err) => { console.log(err.message); }); console.log('Hey man'); /* Hey man meeting not found */

দেখুন যদি এখাবে টাইম নিচ্ছে না তবু ও এখানে যখন প্রমিস পাবে তাকে পরের কোড গুলো আগে রান হয়ে যাবে তারপর বাকি কাজ। অ্যাসিঙ্ক্রোনাস ভাবে কাজ হচ্ছে।

এখন ধরুন আমাদের একটা প্রমিস এর resolve ডাটা থেকে আরেকটি প্রমিস রিটার্ন হবে। তখন কিভাবে হ্যান্ডেল হয় চলুন তা দেখে আসি।

const hasMeeting = true; const meeting = new Promise((reslove, reject) => { if (hasMeeting) { const meetingDetails = { name: 'Technical meeting', location: 'Google meet', time: '10:00PM', }; reslove(meetingDetails); } else { const error = new Error('No meeting found'); reject(error); } }); const addToCalender = (meetingDetails) => { /* return new Promise((reslove, reject) => { const calender = `${meetingDetails.name} has been scheduled on ${meetingDetails.location} at ${meetingDetails.time}`; reslove(calender); }); */ // in short const calender = `${meetingDetails.name} has been scheduled on ${meetingDetails.location} at ${meetingDetails.time}`; return Promise.resolve(calender); // we know this code only work only when we get meetingDetails properly so we don't need reject(). so we can declare simplely }; meeting .then(addToCalender) .then((res) => { console.log(res); }) .catch((err) => { console.log(err.message); }); //output - Technical meeting has been scheduled on Google meet at 10:00PM

দেখুন আগে meeting() থেকে অব্জেক্ট কে স্ট্রিংগিফাই টা পেতাম এখন আমরা যেহেতু .then(calender) কে পাঠাচ্ছি তাই তাই তার থেকে calender টা পাচ্ছি।

আর এখানে প্রমিস একটা ভালো দিক হলো এখানে যতই আমরা .then ব্যবহার করি না কেন তার জন্য একটা .catch ব্লক এ যথেষ্ট সবগুলো জন্য এরর হ্যান্ডেল করবে এই .catch ব্লক। কোনো .then ব্লক এ এরর আসলে সরাসরি তা .catch ব্লক এ চলে যাবে এবং এরর সো করবে।

চলুন কিছু প্রমিস এর আরো কিছু ব্যবহার দেখি আসি। ধরুন আমাদের কাছে ২ টি প্রমিস আছে আমরা তাদের কে একসাথে পেতে চাই তাহলে আমরা Promis.all() ব্যবহার করতে পারি এখানে প্যারামিটার একটি অ্যারে নিবে প্রমিস গুলো এবং একসাথে আউটপুট গুলোর একটু অ্যারে রিটার্ন করবে।

const promise1 = Promise.resolve('Promise 1 resolved'); const promise2 = new Promise((resolve, reject) => { setTimeout(() => { resolve('Promise 2 resloved'); }, 2000); }); Promise.all([promise1, promise2]).then((res) => { console.log(res); }); // output - [ 'Promise 1 resolved', 'Promise 2 resloved' ]

আরেকটি মেথড আছে সেটা হলো Promise.race() এটা ব্যবহার করলে যতগুলো প্রমিস আমার অ্যারেতে দিবো তাদের মধ্যে যে সবার আগে resolve হবে শুধু তাকে রিটার্ন করবে। আমাদের ক্ষেত্রে promise1 এর কোনো সময় লাগছে না তাই সে আগে রিটার্ন হবে।

Promise.race([promise1, promise2]).then((res) => { console.log(res); }); // output - Promise 1 resolved

এখন এই .then & .catch ও কিন্তু অতটাও ফ্রেন্ডলি না। বড় অ্যাপ্লিকেশনে একটি .then ব্লকে অনেক অনেক কোড থাকতে পারে এর ফলে আমার ব্লক গুলো অর্ডার ফ্লো কন্ট্রোল করা একটু কঠিন হতে পারে। তাই আমারদের জন্য ভাল হয় যদি আমার অ্যাসিঙ্ক্রোনাস কাজ গুলো সিঙ্ক্রোনাস ভাবে লিখে হ্যান্ডেল করতে পারি। লাকিলি জাভাস্ক্রিপ্ট এখন তা করা যাচ্ছে আর এখানে আসে অ্যাসিঙ্ক-অ্যাওয়েট ( async await ) । চলুন জেনে আসি এর সমপর্কে।

অ্যাসিঙ্ক-অ্যাওয়েট ( async await )

চলুন একটা সিম্পল ফাংশন নিই

function friendlyFunc() { return `Hello`; } console.log(friendlyFunc()); // output - Hello

এখন ফাংশনটিকে যদি Promise করতে চাই তাহলে আমরা return Promise.resolve('Hello'); করতে পারি কিন্তু যদি আমরা ফাংশন এর পূর্বে async কিওয়ার্ড টা ব্যবহার করি তাহলে এটা একটি প্রমিস রিটার্ন করবে।

async function friendlyFunc() { return `Hello`; } console.log(friendlyFunc()); // output - Promise - { 'Hello' }

কিন্তু আমরা যেকোনো ফাংশন কেউ async কিওয়ার্ড দিবো না । সেই ফাংশন অ্যাসিঙ্ক্রোনাস ভাবে কাজ করে অথবা কোনো ডাটা কে ফেচ করে ডাটা আনার কাজ করে তার ফাংশন এর পূর্বে আমরা এই async কিওয়ার্ড টা দিব। এখন আমরা ত পূর্বে Promise পেলে তাকে .then দিয়ে হ্যান্ডেল করতাম এখন এখানে, যেখানে অ্যাসিঙ্ক্রোনাস কাজ টা হবে তার পূর্বে await কিওয়ার্ডটি ব্যবহার করতে হবে। তাহলে ডাটা টি আশার আগ পর্যন্ত অপেক্ষা করবে ডাটা আসলে তারপর কোডে যাবে। চলুন উপরের আমাদের তৈরি করা meeting অ্যান্ড addTocalender প্রমিস গুলো কে এবার async await দিয়ে হ্যান্ডেল করতে পারি কি না।

const hasMeeting = true; const meeting = new Promise((reslove, reject) => { if (hasMeeting) { const meetingDetails = { name: 'Technical meeting', location: 'Google meet', time: '10:00PM', }; reslove(meetingDetails); } else { const error = new Error('No meeting found'); reject(error); } }); const addToCalender = (meetingDetails) => { const calender = `${meetingDetails.name} has been scheduled on ${meetingDetails.location} at ${meetingDetails.time}`; return Promise.resolve(calender); }; async function myMeeting() { const meetingDetails = await meeting; const calender = await addToCalender(meetingDetails); console.log(calender); } myMeeting(); // output - Technical meeting has been scheduled on Google meet at 10:00PM

অর্থাৎ আমরা এবার একই কাজ সুন্দর ভাবে লাইন বাই লাইন করতে পারছি।

এখানে দেখুন আমরা একটা ফাংশন নিলাম myMeeting() নামে এখানে আমাদের তৈরি করা প্রমিস গুলো কে লিখব কিন্তু আমাদের প্রমিস ফাংশন গুলো এখানে তেমন কিছু নাই যে একটু সময় নিবে। কিন্তু রিয়েল লাইফে অ্যাপিআই কল করলে তা একটু সময় নিবে তাই এটা অ্যাসিঙ্ক্রোনাস কাজ এখানে একটু ওয়েট করতে হবে তাই এখানে যে ফাংশন প্রমিস রিটার্ন করবে, যেটা অ্যাসিঙ্ক্রোনাস কাজ তার পূর্বে await লিখতে হবে এখন await লিখতে পুরা ফাংশন টা async হতে হবে তাই myMeeting() পূর্বে async দিতে হয়েছে।
এখন awit meeting থেকে পাওয়া ডাটাকে আমরা meetingDetails এ রাখলাম। এখন addToCalender একটা ফাংশন যা প্রমিস রিটার্ন করে এবং প্যারামিটার আকারে meetingDetails নেয়। আমার addToCalender কে await রেখে কল করে পূর্বে পাওয়া meetingDetails কে পাস করে দিলাম। এখন আমাদের addToCalender ফাংশন তো লেখাই আছে সে meetingDetails নিয়ে একটা calender দিবে তারপর myMeeting() থেকে calender কে কন্সোল করে দিলাম। তারপর সবার শেষে myMeeting() টাকে কল করে দিলাম। আমাদের কোড ঠিক ঠাক ভাবে কাজ করছে।

এখন এখানে আমরা এরর কন্ট্রোল করব। এর জন্য আমরা try catch ব্যবহার করতে পারি। যেখানে অ্যাসিঙ্ক্রোনাস কাজ গুলো থাকবে তাদের কে try {} ব্লকে রেখে দিবো। তার পর catch () {} ব্লকে আমরা এরর হলে কি হবে তা হ্যান্ডেল করতে পারি। চলুন কোডে দেখি নি।

const hasMeeting = false; const meeting = new Promise((reslove, reject) => { if (hasMeeting) { const meetingDetails = { name: 'Technical meeting', location: 'Google meet', time: '10:00PM', }; reslove(meetingDetails); } else { const error = new Error('No meeting found'); reject(error); } }); const addToCalender = (meetingDetails) => { const calender = `${meetingDetails.name} has been scheduled on ${meetingDetails.location} at ${meetingDetails.time}`; return Promise.resolve(calender); }; async function myMeeting() { try { const meetingDetails = await meeting; const calender = await addToCalender(meetingDetails); console.log(calender); } catch (err) { console.log(err.message); } } myMeeting(); // output - No meeting found

দেখুন আমরা অ্যাসিঙ্ক্রোনাস কাজ সিঙ্ক্রোনাস ভাবে করে ফেলছি লাইন বাই লাইন। কি মজার তাই না। দেখে বুঝতে সহজ এবং পরে কন্ট্রোল করতে অনেক ইজি। তাই আমরা সব সময় অ্যাসিঙ্ক্রোনাস কাজ গুলো এভাবে async await দিয়ে সহজে সিঙ্ক্রোনাস ভাবে করব। ❤❤