নিত্যদিনের জাভাস্ক্রিপ্টঃ প্রমিস (Promise)

জাভাস্ক্রিপ্ট এর অ্যাসিনক্রোনাস আচরণ সম্পর্কে আমরা জানি। রিমোট কোনো সার্ভার থেকে বা একটু সময় লাগে এমন কোনো অপারেশন শেষ করার জন্যে জাভাস্ক্রিপ্ট অপেক্ষা না করে বরং পরের অপারেশনে চলে যায়। এধরনের অপারেশন হচ্ছে অ্যাসিনক্রোনাস অপারেশন। এখন এই অপারেশন ফেলে অন্য অপারেশনে চলে গেলেও জাভাস্ক্রিপ্ট ঠিকই এটাকেও ট্র্যাক করে রাখে। আর এইজন্যেই জাভাস্ক্রিপ্ট প্রমিস(Promise) ব্যবহার করে।

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

প্রমিস তৈরী করাঃ

const aPromise = control => { 
   return new Promise((resolve, reject) => {
      setTimeout(() => {
         if(control) {
            resolve();
         } else {
            reject();
         }
      }, 3000)
   })
}

এখানে আমরা একটা ফাংশন তৈরী করেছি, যেটার একটা আর্গুমেন্ট নিবে। এখন এই ফাংশন প্রমিস রিটার্ণ করবে। প্রমিস দুইটা আর্গুমেন্ট নেয়, resolve আর reject । তারপর আমরা ভিতরে setTimeout দিয়ে একটা ফেইক টাইম লাগতে পারে এমন অপারেশন তৈরী করেছি ৩০০০ মিলিসেকেন্ড(৩ সেকেন্ড) টাইম দিয়ে। তারপর আসলে মজার ব্যাপার। আমাদের প্রমিস যদি তখনি সফল হবে যদি আমরা আমাদের কাঙ্খিত ফলাফল পাই। এখন আমরা এই ফাংশনে(aPromise) একটা আর্গুমেন্ট নিয়েছি, এটা দিয়ে আমরা true অথবা false পাস করে প্রমিস সফল না ব্যর্থ সেরকম একটা কন্ডিশন তৈরী করবো। যদি আর্গুমেন্ট টা true হয় তাহলে আমাদের প্রমিস সফল হয়েছে এবং আমরা resolve() কল করবো। আর যদি false হয় তাহলে reject() কল করার মাধ্যমে প্রমিস ব্যর্থ হয়ে যাবে।

এখন আমরা আমাদের ফাংশনটা true আর্গুমেন্ট দিয়ে কল করলেঃ

aPromise(true);

এরকম কিছু একটা আউটপুট দেখতে পাবেনঃ

প্রমিস হ্যান্ডল করাঃ

এখন আমরা প্রমিস সফল বা ব্যর্থ হয়েছে কিনা সেটা হ্যান্ডল করবো। সেক্ষেত্রে যদি প্রমিস সফল হয় তাহলে আমাদের aPromise ফাংশনের সাথে .then() চেইন করে এখানে একটা কলব্যাক ফাংশন দিতে পারবো, যেটা প্রমিসে সফল বা অন্যকথায় resolve হলে রান হবেঃ

aPromise(true)
  .then(() => {
     console.log('This is a Success');
  })

এটা তিন সেকেন্ড পরে true আর্গুমেন্ট হিসেবে দেওয়ায় প্রমিস resolve হয়ে আমাদের কলব্যাক রান করবেঃ

আর যদি প্রমিস রিজেক্ট হয় তাহলে আমাদের সেটা আরেকটা চেইন অপারেশন .catch() এর মধ্যে কলব্যাক ফাংশন দিয়ে হ্যান্ডল করতে হবে। এখন আমরা কিন্তু জানিনা যে অপারেশন সফল না ব্যর্থ হবে। সেক্ষেত্রে আমাদের .then() আর .catch() দুইটাই রাখতে হবে। .catch() রান করবে কোনো কারণে যদি আমাদের প্রমিস reject হয়ঃ

aPromise(false)
  .then(() => {
     console.log('This is a Success');
  })
  .catch(() => {
     console.log('This is a Failure');
  })

এখানে যেহেতু আর্গুমেন্ট এ false দিয়েছি, তাই প্রমিস থেকে reject হবে। আর তাই ৩ সেকেন্ড পরে অপারেশন শেষ হয়ে catch ব্লকের ভিতরের কলব্যাক রান করবেঃ

এখানে প্রথমে আমরা যে aPromise ফাংশন দিয়ে প্রমিস রিটার্ণ করলাম, সেই প্রমিস তৈরী করার পার্টটা বেশীরভাগ ক্ষেত্রেই আমাদের লেখা লাগে না। বরং আমরা যে সিস্টেম ব্যবহার করে ডাটা আদান-প্রদান করি, বা লাইব্রেরী ব্যবহার করি সেগুলোতেই এই অংশটা কোড করা থাকে কখন কিভাবে প্রমিস resolve করবে নাকি reject করবে এসব। আমাদের বেশীর ভাগ ক্ষেত্রে রিটার্ণ হওয়া প্রমিস .then() আর .catch() দিয়েই হ্যান্ডল করতে হয়। আবার ভিতরে যে কলব্যাক গুলো ব্যবহার করা হয়, সেসব কলব্যাকে বেশীরভাগ ক্ষেত্রে আমাদের রিমোট সার্ভার থেকে যে ডাটা চাই, সেগুলো আর্গুমেন্ট হিসেবে আসে। আমরা কলব্যাকের ভিতর থেকে সেই আর্গুমেন্ট এর সাহায্যে সেগুলো অ্যাক্সেস করতে পারি। যেমন আগের প্রমিসটা একটু মডিফাই করলে, আমরা চাইলে resolve() বা reject() থেকে ডাটা পাঠাতে পারিঃ

const aPromiseWithData = control => { 
   return new Promise((resolve, reject) => {
      setTimeout(() => {
         if(control) {
            resolve('Simple Success Data');
         } else {
            reject('Simple Error Data');
         }
      }, 3000)
   })
}

এখানে resolve() বা reject() থেকে ঠিক যেভাবে ডাটাগুলো পাঠানো হয়েছে সেভাবেই আমরা .then() বা .catch() এর কলব্যাক থেকে অ্যাক্সেস করতে পারবো আর্গুমেন্ট হিসেবে অ্যাক্সেপ্ট করেঃ

aPromiseWithData(true)
  .then((data) => {
     console.log(data);
  })

দেখুন এই ডাটা আমাদের প্রমিস থেকে এসেছে, তারপর এখানে resolve() হয়ে .then() এর কলব্যাকে আর্গুমেন্ট হিসেবে এসেছে, যেটা আমরা পরে অ্যাক্সেস করে প্রিন্ট করতে পেরেছিঃ

একইভাবে প্রমিস reject হলেওঃ

aPromiseWithData(false)
  .then((data) => {
     console.log(data);
  })
  .catch((err) => {
     console.log(err);
   })

একাধিক প্রমিস হ্যান্ডল করাঃ এরকম কোনো কোনো সময় আমাদের একাধিক প্রমসিও হ্যান্ডল করতে হতে পারে। যেমন ধরি আমাদের দুইটা প্রমিস আছেঃ

const promise1 = new Promise((resolve, reject) => {
   setTimeout(() => {
      if(true) {
         resolve('Promise 1 Resolved');
      } else {
         reject('Promise 1 Error');
      }
   }, 5000)
 })

আমরা এভাবেও সরাসরি প্রমিস তৈরী করতে পারি। নিচে আরেকটা প্রমিস তৈরী করলামঃ

const promise2 = new Promise((resolve, reject) => {
   setTimeout(() => {
      if(true) {
         resolve('Promise 2 Resolved');
      } else {
         reject('Promise 2 Error');
      }
   }, 5000)
})

এখন এই দুইটা প্রমিস কমপ্লিট হওয়ার পর কোনো অপারেশন চালাতে চাইলে আমরা দুইটা প্রমিসকে অ্যারে আকারে এভাবে নিতে পারিঃ

Promise.all([promise1, promise2]);

তারপর .then() দিয়ে কলব্যাক কল করতে পারবো যেটা এই দুইটা প্রমিস কমপ্লিট হলে পরেই রান করবে। আর এই প্রমিসগুলো থেকে আসা ডাটাগুলো এই কলব্যাক ফাংশনে অ্যারে আকারে আসবেঃ

Promise.all([promise1, promise2]).then((dataArr) => {
   console.log(dataArr);
})

ব্যাস এবার setTimeout এর টাইম শেষ হয়ে গেলে আপনার প্রমিসের ফলাফল দেখতে পাবেনঃ

fetch() অ্যাপিআইঃ

এখন আমরা সত্যিকারের রিমোট সার্ভার থেকে ডাটা এনে সেটা নিয়ে কাজ করবো, এজন্যে আমরা ব্রাউজারের fetch() অ্যাপিআই ব্যবহার করবো। এই অ্যাপিআই এর কাজ হচ্ছে বাইরের রিমোট কোনো সার্ভার থেকে রিসোর্স আনা। এই অংশটা নোড জেএস এ ঠিক সরাসরি কাজ করবে না। যদিও নোড জেএস এ চাইতেও আরো ভালো ভালো লাইব্রেরী আছে, তবে এটা যেহেটু মডার্ণ ব্রাউজারগুলোতে বিল্ট-ইন ভাবেই আছে, তাই আমরা এখানে এটাই ব্যবহার করবো। এখন এই fetch() অ্যাপিআই নিজে নিজেই প্রমিস রিটার্ণ করার প্রসেসটা হ্যান্ডেল করে। আমাদের জাস্ট রিটার্ণ হওয়া প্রমিসটা হ্যান্ডেল করতে হবে। আমরা একটা একটা একটা রিমোট সার্ভার থেকে ফেইক কিছু ডাটা সত্যিকারেরই আনবোঃ

const dataFromRemote = fetch('https://jsonplaceholder.typicode.com/posts');

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

console.log(dataFromRemote);

হ্যা! এটা প্রমিসই। এখন আমরা এটাকে সহজেই .then() .catch দিয়ে সহজেই হ্যান্ডল করতে পারবো। আর এই ফেচ অ্যাপিআই ফেচ করা ডাটাগুলো .then এর কলব্যাকে আর্গুমেন্ট হিসেবে পাস করে। আর কোনো এরর হলে .catch এর কলব্যাকে আর্গুমেন্ট হিসেবে পাস করেঃ

dataFromRemote.then((data) => {
   console.log(data);
})

এখন এখানে data তে কিছু ডাটা দেখতে পারবেন। এগুলোই আপনার প্রমিস resolve হওয়ার কারণে সাক্সেস টাইপের কিছু ডাটাঃ

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

const dataFromRemoteFailed = fetch('https://jsonplaceholderdoesntexist.typicode.com/posts');

আপনি যদিও আপনার কন্সোলে প্রমিস রিজেক্ট হওয়ার ম্যাসেজ পাবেন। তবে আসল ম্যাসেজ আপনি .catch() থেকে পাবেন যেখানে আপনি এই এররটাকে হ্যান্ডল করতে পারবেনঃ

dataFromRemoteFailed.then((data) => {
   console.log(data);
}).catch((err) => {
   console.log('Your Error:', err);
})

এখন আমাদের প্রমিস resolve হয়ে গেলে কিন্তু catch ব্লকের কোনো দরকারই পড়ে না। কিন্তু এটাও মোটেও ভালো প্র্যাক্টিস না যে আপনি catch ব্লক না রেখেই আপনার অ্যাপ্লিকেশনে সব প্রমিস হ্যান্ডল করে ফেললেন। এরর হতেই পারে, যেকোনো কারণেই হতে পারে। আর সেজন্যে এই catch ব্লক রেখে সেটাকে হ্যান্ডল করাটাই ভালো প্র্যাক্টিসের মধ্যে পড়ে।

আসল ডাটা উদ্ধার করাঃ

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

fetch('https://jsonplaceholder.typicode.com/posts')
   .then((data) => {
      return data.json();
   }).then((posts) => {
      console.log(posts);
})

এখানে আমাদের এই লিঙ্কে থাকা ১০০ টা অ্যারেসহ সব ডাটা চলে আসবেঃ

শেয়ার করুন

লেখাটি ভাল লাগলে সোশ্যাল মিডিয়ায় শেয়ার করুন। আপনার কলিগ, বন্ধু কিংবা প্রিয় কারও কাজে লাগতে পারে। জানেন তো, শেয়ারিং ইজ কেয়ারিং!

সাবস্ক্রিপশন সেন্টার

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