Skip to content

Chapter 19: JavaScript JSON Fetch and Promise

19-1: তিন লেয়ারের JSON শাসন

JSON Three Layers

আমাদের সবার হাতে একটা ম্যাজিক বাক্স আছে, যার জাদুমন্ত্রে আমরা দিনের 24 ঘণ্টার মধ্যে 48 ঘণ্টা ফিদা হয়ে থাকি। সেটা আর কিছু না, সেটা হলো– মোবাইল ফোন। এই মোবাইল ফোন দিয়ে আমরা কত কী করি। সময় নষ্ট করি, আবার ভালো কাজও করি। যেমন ধর, তোর বন্ধু তোর কাছ থেকে টাকা ধার নিয়ে বান্দরবান গিয়ে মাস্তি করতেছে। তোর টাকা ফেরত দেয়ার কোনো নাম-গন্ধ নাই। তুই প্রতিদিন সকাল-বিকাল না হলেও 100 বার কল দেস, তাও সে টাকা দেয় না। আবার তোর ফোন রিসিভও করে না।

এই করতে করতে একদিন তোর মনে প্রশ্ন আসলো, আচ্ছা– আমার এইখান থেকে বন্ধুর ( টাকা মেরে দেয়া বন্ধু ) ফোনে কল যায় কীভাবে? এমন না যে– আমার ফোনের সাথে তার ফোনের সাথে সরাসরি কোনো তার বা লাইন বা কিছু ডাইরেক্ট কানেক্ট করা আছে।

তোর ফোন প্রথমে নিকটবর্তী মোবাইল টাওয়ারের সাথে সংযুক্ত হয়। তারপর সেই টাওয়ার থেকে আরও বিভিন্ন টাওয়ারের মাধ্যমে তোর বন্ধুর নিকটবর্তী টাওয়ারে পৌঁছায়। পুরো প্রসেসটি এত দ্রুত ঘটে যে, মনে হয় সরাসরি কথা বলছি। ঠিক এই টাওয়ারগুলোর মাধ্যমেই ইন্টারনেটও কাজ করে।

ইন্টারনেটে শুধু তোর ফোনই নয়, তোর ল্যাপটপ, প্রিন্টারসহ সবকিছুতে যুক্ত থাকে। বর্তমানে ইন্টারনেট ছাড়া আমাদের দৈনন্দিন জীবন কল্পনাই করা যায় না। যেমন, তোর বন্ধুর একটি নাম্বার থাকে, ঠিক তেমনই প্রতিটি ওয়েবসাইটেরও একটি IP অ্যাড্রেস থাকে। এই IP অ্যাড্রেসগুলো DNS (Domain Name System)-এর মাধ্যমে পরিচালিত হয়, যা ওয়েবসাইটগুলোকে সহজে খুঁজে পেতে সাহায্য করে।

ধর, তুই কোনো ডাটা পাঠাচ্ছোস। সেই ডাটা অনেক ছোট প্যাকেটে বিভক্ত হয়ে ইন্টারনেটের মাধ্যমে তোর বন্ধুর কাছে পৌঁছে যায়। এই প্রসেসটি HTTP বা HTTPS-এর মাধ্যমে সম্পন্ন হয়। HTTPS আরও সুরক্ষিত। কারণ, এতে ডাটাগুলো এনক্রিপ্ট থাকে, যার ফলে কোনো তৃতীয় পক্ষ তা পড়তে পারে না।

তুই কম্পিউটার বা মোবাইল ডিভাইসে ওয়েবসাইট ইউজ করস। তুই ধর, ইউটিউবে ভিডিও দেখতেছস। তুই ইউটিউব অ্যাপ বা ব্রাউজার দিয়ে ভিডিও প্লে করার রিকোয়েস্ট পাঠাইলি (এটা হলো ক্লায়েন্ট সাইড)। ইউটিউবের সার্ভার সেই ভিডিওর লিংক আর লজিক প্রসেস করে ইন্টারনেটের মাধ্যমে তোর কাছে পাঠালো (এটা হলো সার্ভার সাইড)।

আর ভিডিও, ডেসক্রিপশন, লাইক, ভিউস— এইগুলা স্টোর করা থাকে ডাটাবেজে, যেখান থেকে সার্ভার সেই ডেটা রিকোয়েস্ট অনুযায়ী বের করে পাঠায় ডাটাবেজ থেকে। তিনটা জিনিস একসাথে মিলে কাজ করে বলে তুই ওয়েবসাইট বা অ্যাপ এত সহজে ব্যবহার করতে পারস। অর্থাৎ টোটাল তিনটা পার্ট থাকে।

১। ক্লায়েন্ট সাইড ( কম্পিউটার বা মোবাইল)

২। সার্ভার সাইড

৩। ডাটাবেজ

এখন প্রশ্ন করতে পারস, এই যে ডাটা, এইটা কী জিনিস। এইটা কি লাল শাক, ডাটা শাকের মতো রান্না করে খাওয়ার জিনিস?

উত্তর হচ্ছে, এই ডাটা মানে তথ্য বা ইনফরমেশন বা তুই বলতে পারস কোন ভেরিয়েবল, অ্যারে, অবজেক্ট ইত্যাদি। তবে উল্টাপাল্টাভাবে ডাটা সেন্ড না করে অর্গানাইজভাবে পাঠানোর অনেক সিস্টেম আছে। তাদের মধ্যে সবচেয়ে জনপ্রিয় জাভাস্ক্রিপ্ট অবজেক্ট নোটেশন JavaScript Object Notation (JSON)।

নিচে user নামে একটা জাভাস্ক্রিপ্টের অবজেক্ট আছে। এইটাকে যদি JSON.stringify(user) ব্যবহার করে, তাহলে অবজেক্টটি একটি JSON স্ট্রিংয়ে কনভার্ট করা হয়।

javascript
  const user = {
    id: 1,
    name: 'Amir',
    job: 'Actor'
  };

  const userJSON = JSON.stringify(user)
  console.log(userJSON);

Output:
{
 "id": 1,
 "name": "Amir",
 "job": "Actor"
}

এখন, JSON স্ট্রিং এবং জাভাস্ক্রিপ্ট অবজেক্টের মধ্যে কিছু পার্থক্য দেখা যায়—

১) JSON স্ট্রিংয়ের প্রোপার্টি নামগুলো ডাবল কোটেশনে থাকে। জাভাস্ক্রিপ্টে থাকে না।

২) জাভাস্ক্রিপ্টের অবজেক্টের মধ্যে কোনো প্রোপার্টির মান হিসেবে ফাংশন থাকতে পারে। যেটাকে আমরা মেথড বলে থাকি। তবে জেশনের মধ্যে ভ্যালু হিসেবে স্ট্রিং, সংখ্যা, বুলিয়ান, array, অবজেক্ট থাকতে পারে। তবে ফাংশন থাকে না।

৩) জাভাস্ক্রিপ্ট অবজেক্ট লেখার সময় স্ট্রিং মানের জন্য সিঙ্গেল কোটেশন ইউজ করতে পারি। তবে JSON-এর স্ট্রিং মানের জন্য ডাবল কোটেশন ইউজ করতে হয়।

এবার আরেকটা বড়সর ডাঙর সাইজের অবজেক্ট দেখ—

javascript
  const shop = {
    owner: 'Alia',
    address: {
      street: 'kochukhet voot er goli',
      city: 'ranbir',
      country: 'BD'
    },
    products: ['laptop', 'mic', 'monitor', 'keyboard'],
    revenue: 45000,
    isOpen: true,
    isNew: false
  };

    const shopJSON = JSON.stringify(shop);
    console.log(shopJSON);

Output:
{
 "owner": "Alia",
 "address": {
  "street": "kochukhet voot er goli",
  "city": "ranbir",
  "country": "BD"
 },
 "products": ["laptop", "mic", "monitor", "keyboard"],
 "revenue": 45000,
 "isOpen": true,
 "isNew": false
}

জাভাস্ক্রিপ্ট অবজেক্টকে JSON-এ কনভার্ট করার জন্য JSON.stringify ব্যবহার করে এবং JSON স্ট্রিংকে পুনরায় অবজেক্টে কনভার্ট করার জন্য JSON.parse ব্যবহার করে।

javascript
const shopObj = JSON.parse(shopJSON);
console.log(shopObj);

এভাবেই জাভাস্ক্রিপ্ট অবজেক্টকে JSON স্ট্রিংয়ে কনভার্ট করা হয়। আবার জেশন স্ট্রিংকে জাভাস্ক্রিপ্ট অবজেক্টে কনভার্ট করা যায়।

Practice:

  1. জাভাস্ক্রিপ্ট, অবজেক্ট ও JSON স্ট্রিংয়ের মধ্যে কী কী পার্থক্য?
  2. একটা অবজেক্ট বানা, যেখানে একটা user থাকবে। user-এর মধ্যে name, email, address, এবং একটা order history থাকবে। সেই order history-তে অন্তত তিনটা প্রোডাক্ট থাকবে। JSON.stringify দিয়ে পুরা অবজেক্টটাকে JSON স্ট্রিংয়ে কনভার্ট কর।
  3. shopping cart অবজেক্ট বানা, যার মধ্যে products (array of products), total price (সবগুলা প্রোডাক্টের টোটাল প্রাইস) এবং user details (name, id, contact) থাকবে। এরপর এটাকে JSON স্ট্রিংয়ে কনভার্ট কর।
  4. একটি weather অবজেক্ট বানা, যার মধ্যে city, temperature, humidity এবং forecast (array) থাকবে। forecast array-তে অন্তত 7 দিনের সম্ভাব্য টেম্পারেচার থাকবে। এটাকে জেশনে কনভার্ট কর।
  5. সিনেমার জন্য movie-এর ডিটেইল থাকবে। যেমন title, release year, actors এবং ratings। actors একটি array হবে (যেখানে actor দের নাম থাকবে) এবং ratings একটি number। তারপর JSON.stringify দিয়ে কনভার্ট কর। কনভার্ট করার পর সেটাকে আবার জাভাস্ক্রিপ্ট অবজেক্টে কনভার্ট কর।
  6. এইবার project management system বানা। যেখানে অনেকগুলা প্রজেক্ট থাকবে একটা অ্যারের মধ্যে। প্রত্যেকটা প্রজেক্টের মধ্যে প্রজেক্টে নাম, project description, team members (array), deadlines এবং tasks। প্রতিটি task-এ title, assignee এবং status থাকবে। JSON.stringify দিয়ে কনভার্ট কর।
  7. লার্নিং প্লাটফর্মের জন্য courses-এর কিছু ডাটা বানা। সেখানে মিনিমাম 3টা কোর্স থাকবে। প্রত্যেকটা কোর্সে course title, instructor name, duration এবং lessons (array)। lessons array-তে lesson name, duration এবং difficulty level থাকবে। ডিফিকাল্টি লেভেল বলতে beginner, intermediate, advanced যেকোনো একটা মান হবে। JSON.stringify দিয়ে কনভার্ট কর।
  8. ইকমার্স ওয়েবসাইটে product review-এর ডাটা বানিয়ে ফেল। যেখানে প্রত্যেকটা রিভিউ অবজেক্টে product name, reviewer details (name, email), rating, and review text থাকবে। এরপর সেই অবজেক্টটিকে JSON.stringify দিয়ে কনভার্ট কর। সেই json-কে আবার জাভাস্ক্রিপ্ট অ্যারেতে কনভার্ট কর।

19-2: Promise রক্ষাকারী প্রেমিক

কমবয়সি পোলাপান প্রেম করতে গিয়ে খেয়ে না খেয়ে ডজনে ডজনে প্রমিজ করতে থাকে। যেমন: 'আজীবন তোমার সাথে থাকব', 'তোমাকে হাতি ঘোড়া কিনে দিব', 'কখনো তোমাকে দুঃখ দিব না', 'দুই ডজন বাচ্চাকাচ্চা দিব' ইত্যাদি। কিন্তু বাস্তবে কি সবাই তাদের দেয়া প্রতিশ্রুতি রাখতে পারে?

উত্তর হচ্ছে, সবাই পারে না। কেউ কেউ পারে, আবার কেউ কেউ বলে– আমার ফ্যামিলি মানবে না, বড় ভাইয়ের বিয়ে হওয়ার আগে কিছুই বলতে পারব না। এইসব হাবিজাবি বলে কেটে পড়ে।

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

এই pending অবস্থার পরে হয়তো তুই প্রমিজটা পূরণ করবি। এটাকে বলে প্রমিজ resolve করা। আবার তুই প্রমিজ ভুলেও যেতে পারিস বা ধোঁকা দিতে পারিস, এটাকে বলে প্রমিজ reject করা।

অর্থাৎ একটা প্রমিসে তিনধরনের অবস্থা (state) থাকে—

১) যে মুহূর্তে তুই ওয়াদাটা করতেছস, তখন প্রমিজ pending অবস্থায় থাকে।

২) ফিউচারে তুই ওয়াদা ফুলফিল করতে পারস। প্রমিজ resolve করতে পারস। যেমন চাকরি হয়ে যাওয়ার পর সত্যি সত্যি বিয়ে করলি। সুদিন ফেরত পাওয়ার পর তাকে ছেড়ে চলে গেলি না। তার সাথেই জীবন কাটালি।

৩) অথবা ওয়াদাটা পরিপূর্ণ করলি না, প্রমিজ reject করে দিবি।

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

Promise

জাভাস্ক্রিপ্টে Promise নামে একটা অবজেক্ট আছে। একটু আগে যেভাবে বলছিলাম, একজাক্টলি এইভাবেই কাজ করে। নিচে new Promise দিয়ে একটা প্রমিজ তৈরি করতেছি।

এই প্রমিজ ক্রিয়েট করার সময় একটা ফাংশন দিতে হয় প্যারামিটার হিসেবে। বলতে পারস, একটা কলব্যাক ফাংশন দিতে হয়। সেই ফাংশনে দুইটা প্যারামিটার নেয়। প্যারামিটার দুইটাই কিন্তু ফাংশন।

এদের একটার নাম হচ্ছে resolve, আরেকটার নাম হচ্ছে reject। আর নাম দেখেই বুঝা যাচ্ছে, প্রমিজ যদি ফুলফিল হয়, তাহলে resolve নামক কলব্যাক ফাংশনকে কল করবে। আর যদি প্রমিজ ফুলফিল করতে না পারে, তখন reject ফাংশনকে কল করবে। নিচের কোড দেখলেই বুঝতে পারবি।

আর resolve বা reject ফাংশন দুইটাকে কল করার সময় তুই কিছু ডাটা বা তথ্য দিয়ে দিতে পারবি। যেমন, নিচে দুইটার মধ্যেই কিছু ডাটা টেক্সট/স্ট্রিং হিসেবে দিয়ে দিছি।

javascript
const orderFood = new Promise((resolve, reject) => {
  const foodReady = true;

  if (foodReady) {
    resolve("Food is ready!");
  } else {
    reject("Baap er hotel bondo.");
  }
});

যখন তুই new Promise() দিয়ে একটা প্রমিজ বানাবি, সে একটা "Promise অবজেক্ট" রিটার্ন করে। সেই অবজেক্টে অনেকগুলা মেথড থাকে। একটার পর একটা দরকার অনুসারে কল করতে পারবি। সাধারণত তিনটা মেথড ইউজ করা হয়। তাদের নাম then, catch, finally।

javascript
promise.then().catch().finally();

.then() চলে, যখন প্রমিজ resolve হয়।

.catch() চলে, যখন প্রমিজ reject হয়।

.finally() প্রমিজ resolve হোক বা reject হোক, .finally() চলবেই।

এদের প্রত্যেকটা আবার প্যারামিটার হিসেবে একটা করে কলব্যাক ফাংশন নেয়। আবার চাইলে একাধিক then ইউজ করাও যায়।

javascript
promise
  .then(() => {})
  .catch(() => {})
  .finally(() => {});

আমি জাস্ট সিম্পলভাবে একটা then, তারপর একটা catch ইউজ করতেছি।

javascript
  orderFood
    .then((message) => {
      console.log(message);
    })
    .catch((error) => {
      console.log(error);
    });

Output: Food is ready!

এই জন্য orderFood কল করার পর সাথে সাথে .then দিছস। আর .then-এর ভিতরে একটা ফাংশন। সেখানে একটা প্যারামিটার আছে message নামে। তুই চাইলে অন্য নামও দিতে পারতি। তারপর ঠিকঠাকমতো ডাটা পেলে কী করবি, তাই এইখানে করবি। আমরা আপাতত ডাটা message কনসোল লগ করতেছি।

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

এখন আমার উদ্দেশ্য হচ্ছে প্রমিজ লিখব। এই প্রমিজ যখন resolve করবে, তখন রিটার্ন হিসেবে একটা অ্যারে পাবে, সেখানে অনেকগুলা ইউজারের নাম থাকবে। আর যদি reject করে, তখন বলবে, কোনো ইউজারের ডাটা নাই।

javascript
const getUsers = new Promise((resolve, reject) => {
  const usersAvailable = true;
  const users = ["John", "Alice", "Bob", "Charlie"];

  if (usersAvailable) {
    resolve(users);
  } else {
    reject("No user data available.");
  }
});

getUsers
  .then((userNames) => {
    console.log(userNames);
  })
  .catch((error) => {
    console.log(error);
  });

Output: ["John", "Alice", "Bob", "Charlie"];

কেন প্রমিজ দরকার?

জাভাস্ক্রিপ্ট single-threaded মানে একই সময়ে একটা কাজ করতে পারে। কিন্তু অনেক কাজ আছে, যা সাথে সাথে হয় না, যেমন সার্ভার থেকে ডাটা নিয়ে আসা, ফাইল পড়া ইত্যাদি। এই কাজগুলো ব্লক না করে প্রমিজ ব্যবহার করে অ্যাসিনক্রোনাস হ্যান্ডেল করে। আর প্রমিজ যে সব সময় ভালোভাবে কাজ করবে, সেটার কোনো গ্যরান্টি নাই। তাই Resolve আর Reject দুইটার জন্যই কোড লিখে রাখতে হয়।

সব প্রমিজ উড়ায় ফেলব

অনেক সময় এমন হয় যে, তুই একসাথে একাধিক প্রমিজ নিয়ে কাজ করতে চাস। যেমন ধর, একবারে তিনটা API রিকোয়েস্ট মারবি আর চাইবি সবগুলা রেসপন্স একসাথে চলে আসুক অথবা তিনটা টাস্ক করবি, যেগুলা একসাথে শুরু হবে, কিন্তু কোনটা আগে শেষ হবে, সেটা তুই জানস না।

এক্ষেত্রে Promise.all() একসাথে অনেকগুলা প্রমিজ চালায় এবং সবগুলা প্রমিজ resolve হলে একটা array রিটার্ন করে।

সিম্পলভাবে সব প্রমিজ success হলে resolve হবে, আর একটাও fail করলে reject হয়ে যাবে।

javascript
const moneyRequest = new Promise((resolve, reject) => {
  setTimeout(() => resolve("Request submitted!"), 1000);
});

const transferMoney = new Promise((resolve, reject) => {
  setTimeout(() => resolve("Money transferred!"), 2000);
});

const payFee = new Promise((resolve, reject) => {
  setTimeout(() => resolve("Fee paid!"), 1500);
});

Promise.all([moneyRequest, transferMoney, payFee])
  .then((results) => {
    console.log(results);
  })
  .catch((error) => {
    console.log("Error: ", error);
  });

Output: ["Request submitted!", "Money transferred!", "Fee paid!"];

Practice:

  1. একটা প্রমিজ লিখে ফেল। এই প্রমিজ যখন resolve করবে, তখন রিটার্ন হিসেবে একটা অ্যারে পাবে, সেখানে অনেকগুলা ইউজারের নাম থাকবে। আর যদি reject করে, তখন রিটার্ন করে, কোন ইউজারের ডাটা নাই।

  2. তুই একটা পেমেন্ট প্রসেস করার প্রমিজ বানা। সেখানে amount নামে একটা ভেরিয়েবল থাকবে। এই ভেরিয়েবলের মান পজিটিভ হলে (0 এর বেশি হলে) প্রমিজ সফলভাবে প্রসেস হবে। আর যদি এমাউন্টের মান 0 বা তার কম হলে প্রমিজ reject হবে।

  3. এখন sendEmail নামে একটা ফাংশন বানিয়ে ফেল। সেই ফাংশনের ভিতরে একটা প্রমিজ বানিয়ে ফেলবি এবং সেই প্রমিজকে রিটার্ন করবি। এই ফাংশন একটা প্যারামিটার নিবে। প্যারামিটার হিসেবে একটা ই-মেইল নিবি এবং প্রমিজের ওপরে সেই ফাংশনের ভিতরে validEmail-এর একটা অ্যারে থাকবে। যে ই-মেইল প্যারামিটার হিসেবে পাঠানো হয়েছে, সেটা যদি validEmail-এর অ্যারের মধ্যে থাকে, তাহলে প্রমিজ resolve করে বলে দিবে, Email from Nigerian prince। আর যদি ইমেইল এড্রেস validEmail-এর মধ্যে না পায়, তাহলে বলে দিবে, Lets Dance in the spam folder।

19-3: fetch-এর প্যাঁচ

ঘুম থেকে উঠে দেখলি, বালিশের নিচে মোবাইল ফোন নাই। তুই এখন কী করবি? আশেপাশে খুঁজবি। না পেলে চৌকির তলায় খুঁজবি। দেখতে পেলে সেটাকে উঠিয়ে নিয়ে আসবি। এই উঠিয়ে নিয়ে আসার একটা ইংরেজি শব্দ আছে। সেই শব্দকে fetch বলে।

fetch জাভাস্ক্রিপ্টের খুবই গুরুত্বপূর্ণ একটা ফাংশন। ব্রাউজার এইটা দিয়ে সার্ভার থেকে ডাটা নিয়ে আসে। আবার আরও কিছু কাজও করতে পারে। ডাটা নিয়ে আসার কাজটা খুবই সহজ হয়ে যায় fetch ইউজ করলে। এইখানে আরেকটা জিনিস মাথায় রাখবি, fetch কিন্তু ব্রাউজারের জিনিস। ব্রাউজারে কাজ করবে। এই জন্য fetch-কে webAPI বলে অর্থাৎ ওয়েবের api বলে। আর API-এর বলতে বুঝায় Application Programming Interface। সহজ কথায়, API হলো দুইটা সফটওয়্যারের মধ্যে যোগাযোগ করার উপায়।

খুব সিম্পলভাবে fetch করার জন্য জাস্ট fetch-কে কল করবি, আর প্যারামিটার হিসেবে একটা url দিয়ে দিবি। ইউআরএল (url) হচ্ছে এড্রেস বা ঠিকানা। আর ঠিকানা তো লাগবেই। কারণ, তুই কাউকে কল দিলে ফোন নাম্বার লাগবে। কাউকে কোন পার্সেল পাঠালে তার এড্রেস লাগবে। একইভাবে কোনো জায়গা থেকে ডাটা নিয়ে আসতে চাইলে সেটার এড্রেস তো লাগবেই। আর URL (Uniform Resource Locator) সার্ভারে থাকা কোনো রিসোর্স (ডেটা, ফাইল, ইমেজ) খুঁজে বের করার ঠিকানা।

তুই যখন fetch() দিয়ে ডেটা আনার চেষ্টা করবি, তখন URL মাস্ট লাগবে। কারণ, URL ছাড়া বুঝবেই না, কোন জায়গা বা কোন সার্ভার থেকে ডাটা আনতে হবে। এই url একটা স্ট্রিং। নিচে ডাটা লোড করার একটা এড্রেসের উদাহরণ দিলাম।

javascript
const url = "https://jsonplaceholder.typicode.com/users";

এখানে:

https://— এটা প্রটোকল। কীভাবে ডেটা যাবে, সেটা বলে দিচ্ছে।

jsonplaceholder.typicode.com— এটা হোস্ট বা ডোমেইন, মানে সার্ভারের ঠিকানা।

/users— এটা পাথ। সার্ভারে কোন রিসোর্স চাইতেছি, সেটা বলে।

Fetch URL Structure

আর fetch দিয়ে ডাটা লোড করার জন্য fetch লিখে তারপর একটা url দিয়ে দিতে হয়। কেউ কেউ url একটা ভেরিয়েবলে রেখে তারপর সেই ভেরিয়েবলকে fetch-এর আর্গুমেন্ট হিসেবে পাঠিয়ে দেয়। আবার কেউ কেউ সরাসরি fetch-এর মধ্যেই url বসিয়ে দেয়। আর fetch-কে কল করলে সে একটা প্রমিজ অবজেক্ট রিটার্ন করে।

javascript
const url = "https://jsonplaceholder.typicode.com/users";
fetch(url);

// or
fetch("https://jsonplaceholder.typicode.com/users");

আর fetch যেহেতু প্রমিজ রিটার্ন করে, তাহলে প্রমিজ অবজেক্টের ওপরে যেভাবে then, catch, finally ইউজ করা যায়, তার সবই fetch ওপরে ইউজ করা যাবে এবং দরকার হলে এক বা একাধিকবার then চালানো যাবে। একদম প্রমিজের মতো। এমনকি ঠিক প্রমিজের মতো করে then, fetch, finally সবগুলার ভিতরে কলব্যাক ফাংশন দিতে পারবি। ঠিক আগের মতোই। স্ট্রাকচারটা নিচের মতো।

fetch(url) .then() .then() .catch() .finally()

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

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

javascript
fetch("https://jsonplaceholder.typicode.com/users")
  .then((response) => response.json())
  .then((data) => console.log(data))
  .catch((error) => console.log(error));

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

সেই অ্যারো ফাংশনে একটা প্যারামিটার আছে response নামে। আর সেই প্যারামিটারকে অ্যারো ফাংশনের ভিতরে response.json() দিয়ে সার্ভার থেকে যে response পাচ্ছে, সেটাকে জাভাস্ক্রিপ্ট অবজেক্টে কনভার্ট করতেছে। সিম্পলভাবে বলতে পারস, JSON থেকে জাভাস্ক্রিপ্টে কনভার্ট করতেছে, যাতে যে ডাটা সার্ভার থেকে পাওয়া গেছে, সেটাকে তুই জাভাস্ক্রিপ্ট দিয়ে এক্সেস করতে পারস। ইউজ করতে পারস।

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

এরপর .catch() দিয়ে দেখতেছে, যদি ডাটা নিয়ে আসতে না পারে, তখন কী করবে। সেটার ভিতরে একটা সিম্পল কনসোল লগ করতেছে।

ওভারঅল কাজটা হচ্ছে fetch() একটি ফাংশন, যা একটি URL থেকে ডেটা নিয়ে আসে। এটি একটি রেসপন্স দেয়, যা পরে JSON ফরম্যাটে কনভার্ট করা হয়।

Fetch vs Promise

প্রমিজ আর fetch খালাতো ভাই। fetch নিজে প্রমিজ রিটার্ন করে। যেখানে প্রমিজ হচ্ছে জেনারেলভাবে asynchronous কাজের জন্য। আর fetch হচ্ছে স্পেসিফিকভাবে সার্ভার থেকে কোনো কিছু নিয়ে আসার বা সার্ভারে কিছু পাঠানোর জন্য। যেটাকে কঠিনভাবে বললে বলে HTTP রিকোয়েস্টের জন্য।

Practice:

  1. fetch ফাংশন থেকে রিটার্ন করা প্রমিজের মাধ্যমে response.json() কল করে, ডাটা কনভার্ট কেন করা হয়।

  2. fetch কেন ইউজ করা হয়।

  3. fetch আর প্রমিজের মধ্যে ডিফারেন্স কী।

  4. একটা API কল লিখে ফেল। যেটা 'https://jsonplaceholder.typicode.com/users' থেকে ইউজার লিস্ট লোড করবে।

  5. fetch প্র্যাকটিস করে ফেল 'https://jsonplaceholder.typicode.com/users/1' এই ইউআরএল থেকে ডাটা লোড করার জন্য।

19-4: ক্র্যাশ খাইছে CRUD

CRUD Operations

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

এইখানে ভালো করে খেয়াল করলে তুই চারধরনের কাজ করছস।

১. নতুন ই-মেইল লেখা।

২. ই-মেইল পড়া।

৩. ই-মেইলের ড্রাফট এডিট বা আপডেট বা চেইঞ্জ করা।

৪. অপ্রয়োজনীয় ই-মেইল ডিলিট করা।

আমরা যখন ডাটা নিয়ে কাজ করি, তখন ঘুরেফিরে এই চারধরনের কাজ করে।

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

এ ছাড়া মাঝেমধ্যে পুরান ডাটা মডিফাই করা লাগে। যেমন, একাউন্টের পাসওয়ার্ড চেইঞ্জ করবি। হয়তো তোর নাম, ই-মেইল এড্রেস, ছবি— সব আগের মতো থাকবে, শুধু আগের পাসওয়ার্ড চেইঞ্জ হয়ে নতুন পাসওয়ার্ড আসবে। অর্থাৎ শুধু পাসওয়ার্ডের ডাটা চেইঞ্জ হবে। এইটাকে বলে ডাটা আপডেট করা।

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

চারটা কাজকে সিরিয়ালমতো লিখলে হয়—

১. Create: ডাটা ক্রিয়েট করা বা নতুন ডাটা বানানো।

২. Read: আগের বানানো ডাটা নিয়ে আসা, এনে দেখানো।

৩. Update: ডাটার একটা অংশ বা পুরা ডাটা চেইঞ্জ বা মডিফাই করা।

৪. Delete: ডাটা ডিলিট করা।

তুই একটু ভালো করে খেয়াল করলে বুঝতে পারবি, এই যে Create, Read, Update, Delete আছে, এই শব্দগুলোর প্রথম অক্ষর নিয়ে হয় CRUD (উচ্চারণ হবে 'ক্রাড' : যদিও কেউ কেউ 'ত্রুড' বলে), আর এই জন্য ডাটাকে নিয়েই এইসব কাজ করাকে একসাথে অনেকেই বলে CRUD অপারেশন।

আবার অন্যদিকে ডাটা লোড করতে গেলে একটা প্রটোকল বা সিস্টেম ফলো করতে হয়। যেটাকে HTTP (Hypertext Transfer Protocol) বলে। এইটা মূলত ক্লায়েন্ট (যেমন ব্রাউজার) এবং সার্ভারের মধ্যে ডেটা আদান-প্রদান কীভাবে হবে, কী কী জিনিস বলা হলে কী কী করবে, সেই জিনিসগুলোর কিছু রুলস ঠিক করে রাখছে। যাতে সবাই একটা স্ট্যান্ডার্ডভাবে ডাটা বা অন্য জিনিসের Request এবং সেইম স্টাইলে Response করতে পারে।

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

GET: সার্ভার থেকে ডাটা পাওয়া।

POST: সার্ভারে নতুন ডাটা যোগ করা।

PUT: সম্পূর্ণ ডাটা আপডেট।

PATCH: আংশিক ডাটা আপডেট।

DELETE: ডাটা মুছে ফেলা।

এইগুলা হচ্ছে HTTP-এর মেথড বা verb বলে, যেটা ক্লায়েন্ট সাইড থেকে সার্ভার সাইডে কমিউনিকেশনের জন্য কাজে লাগে। অন্যদিকে শুধু ডাটা নিয়ে কাজ করতে গেলে CRUD আসে। তবে দুইটার মধ্যে কাজের পরিধি আলাদা হলেও থিঙ্কিয়ে অনেক মিল আছে।

Create → POST

Read → GET

Update → PATCH/PUT

Delete → DELETE

Read/Get

ডাটা লোড করা বা Get করা খুবই সিম্পল। জাস্ট একটা url দিবি, যেখান থেকে ডাটা লোড করার পারমিশন আছে। পারমিশন থাকলে ডাটা দিয়ে দিবে। পারমিশন না থাকলে ডাটা দিবে না। তারপর fetch মেরে দিবি নিচের মতো করে—

javascript
const url = "https://jsonplaceholder.typicode.com/users";
fetch(url)
  .then((res) => res.json())
  .then((data) => console.log(data));

Create/Post

ডাটা ক্রিয়েট বা পোস্ট করা get-এর কাছাকাছি। শুধু url-এর পরে কমা দিয়ে একটা প্যারামিটার যোগ করতে হয়। যেটাকে অনেকেই option নাম দেয়। সেটার ভিতরে বলে দিতে হয়, http মেথড হবে POST। আবার ডাটা বা Content-type যে JSON, সেটা বলতে হয় headers-এর মধ্যে।

আর সবচেয়ে বড় কথা হচ্ছে, body-এর মধ্যে ডাটা যোগ করতে হয় JSON.stringify-এর ভিতরে। এইটা প্রথম প্রথম বেখাপ্পা লাগবে। ব্যাপার না। করতে করতে কিছুদিন পরে নরমাল হয়ে আসবে।

javascript
const url = "https://example.com/api/user";
const user = { name: "John Doe", email: "john.doe@example.com" };
const options = {
  method: "POST",
  body: JSON.stringify(user),
  headers: {
    "Content-type": "application/json",
  },
};

fetch(url, options)
  .then((res) => res.json())
  .then((data) => console.log(data))
  .catch((error) => console.error(error));

Update (Put, Patch)

PUT আর PATCH কাছাকাছি, তবে PUT ডেটা সম্পূর্ণ পরিবর্তন করতে বা না থাকলে নতুন তৈরি করতে ব্যবহৃত হয়। PATCH শুধু ডাটার একটা অংশ পরিবর্তন করার জন্য ইউজ করা হয়।

ডাটা মডিফাই বা আপডেট করার জন্য PUT ইউজ করলে ক্লায়েন্ট সাইডে অলমোস্ট POST-এর মতো। তবে fetch-এর options-এর মধ্যে মেথডের জায়গায় PUT লিখতে হবে। আগের মতোই url থাকবে, content টাইপ headers-এর মধ্যে json বলে দিতে হবে এবং body-এর মধ্যে ডাটা JSON.stringify-এর ভিতরে দিতে হবে এবং url-এর মধ্যে খেয়াল করবি, একটা আইডি বা সংখ্যা আছে। যেটা দিয়ে সার্ভার সাইড বুঝবে, কার ডাটা আপডেট করতে হবে।

javascript
const url = "https://jsonplaceholder.typicode.com/users/1";
const updatedUser = { name: "John Doe", email: "john.doe@newemail.com" };
const options = {
  method: "PUT",
  body: JSON.stringify(updatedUser),
  headers: {
    "Content-type": "application/json",
  },
};

fetch(url, options)
  .then((res) => res.json())
  .then((data) => console.log(data))
  .catch((error) => console.error(error));

Delete Example

ডিলিট আরেকটু সোজা। জাস্ট url-এর মধ্যে এইটা আইডি বা সংখ্যা থাকবে। যেটা দিয়ে সার্ভার সাইডে বুঝবে, কাকে ডিলিট করতে হবে। আর fetch-এর মধ্যে options-এ বলে দিবি, মেথড হবে ডিলিট, তাহলেই সার্ভার সাইডে পারমিশন থাকলে ডাটা ডিলিট হয়ে যাবে।

javascript
const url = "https://jsonplaceholder.typicode.com/users/1";
const options = {
  method: "DELETE",
};

fetch(url, options)
  .then((res) => res.json())
  .then((data) => console.log("Deleted:", data))
  .catch((error) => console.error(error));

Practice:

  1. প্রোফাইল সিঙ্গেল নাকি মেরিড, এই টাইপের ডাটা চেইঞ্জ করলে PUT না PATCH হবে?

  2. ডাটাবেজে নতুন ইনফরমেশন যোগ করার জন্য কোন HTTP মেথড ব্যবহৃত হয়?

  3. একটা ওয়েবসাইটে যখন ভিডিও দেখতে যাবি, তখন কী ধরনের অপারেশন হয়?

  4. CRUD কী জিনিস?

  5. HTTP-এর মেথড বা verb কী কী আছে? কোনটা কী কাজ করে?

19-5: try করে catch কর

Error Handling

কিছু কিছু কোড একটু রিস্কি হয়। মানে কোডের ভিতর ত্রুটি (Error) চলেও আসতে পারে। আবার নাও আসতে পারে। যেমন ধর, JSON.parse ইউজ করবি। সে কিন্তু প্যারামিটার হিসেবে জেশন স্ট্রিং নিবে। এখন কোনো কারণে যদি JSON ডাটা ঠিকমতো না আসে বা রিটার্ন হিসেবে একটা স্ট্রিং দিয়ে দিছে, তখন কিন্তু এরর দিয়ে ফেলবে।

আর ডাটা লোড করতে এরর খেলে এরর হ্যান্ডেল করতে হয়, নচেৎ ওয়েবসাইট ক্রাশ করতে পারে। ঠিকমতো কাজ করবে না বা ভাঙাচোরা একটা ফিল দিতে পারে।

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

আর এই এক্সট্রা সেইফটিকে বলে try-catch-finally। এই try-catch-finally-এর মধ্যে তিনটা কোডের ব্লক হতে পারে।

try ব্লক

এখানে তুই সেই কোড লিখবি, যেটাতে সমস্যা হতে পারে, আবার না-ও হতে পারে।

catch ব্লক

যদি try-এর ভিতরে কোডে কোনো সমস্যা হয়, কোনো এরর খায়, তাহলে সে catch ব্লকে আসবে। এই catch ব্লকে সাধারণত ইউজারকে এরর ম্যাসেজ দেখানো হয় বা যে জিনিসটার জন্য এরর খাইছে, সেটার একটা ডিফল্ট মান সেট করে দেয়া হয়।

finally ব্লক (অপশনাল)

এটা সবসময় রান করে। মানে এরর হোক বা না হোক, finally ব্লকের কোড শেষ পর্যন্ত চলবেই। যেমন ধর, তুই বই পড়স আর না পড়স, দিনশেষে বই ঠিকই বন্ধ করস। এমন একটা বিষয় আরকি।

এরর খেতে পারে, এমন একটা কোডের অংশ এখন দেখাই। যদি সার্ভার সাইড থেকে জেশন ডাটা আসে, তাহলে সেটাকে আরামসে parse করে ফেলতে পারবে।

javascript
const data = '{"name":"Rahim","age":25,"isStudent":true}';
const result = JSON.parse(data);
console.log(result);

// Output: {name: 'Rahim', age: 25, isStudent: true}

তবে সব সময় যে ভদ্র জেশন ডাটা আসবে, সেটা নাও হতে পারে। কোনো কোনো সময় ডাটা দিতে গিয়ে ডাটা দিতে না পেরে হয়তো কোনো একটা নরমাল স্ট্রিং দিয়ে ফেলল। তাহলে তো জেশনের parse আর কাজ করবে না; বরং উল্টা এরর দিয়ে ফেলবে।

javascript
const data = "Data stolen by data baba";
const result = JSON.parse(data);
console.log(result);

// Output: SyntaxError: Unexpected token 'D', "Data stole"... is not valid JSON

এই ধরনের এরর ঠিকমতো হ্যান্ডেল না করলে ওয়েবসাইট ক্রাশ করে ফেলতে পারে অথবা ওয়েবসাইটের একটা অংশ ঠিকমতো নাও চলতে পারে। তখন try-catch finally-এর মতো সেইফটি ইউজ করতে হয়।

javascript
try {
  const data = "Data stolen by data baba";
  const result = JSON.parse(data);
  console.log(result);
} catch (error) {
  console.log(`Error handled gracefully.`);
} finally {
  console.log("JSON parsing attempt completed.");
}

// Output:
// Error handled gracefully.
// JSON parsing attempt completed.

try-catch-finally রিলেটেড আরেকটা উদাহরণ দেখে ফেলি। এইটা হচ্ছে, ফাইল থেকে কোনো কিছু পড়ার সময় এরর হতে পারে। অনেক সময় দেখা যায়, ফাইল অন্য কোনো এপ্লিকেশন খুলে রাখছে, তখন পড়তে পারে না। আর ফাইল ওপেন করতে না পারলে এরর দিবে। যে ফাইল ক্লোজ কর, নচেৎ আমি ফাইলের ভিতরের জিনিস পড়তে পারতেছি না। তবে ফাইল পড়তে পারুক বা এরর খাক না কেন, শেষ হলে ফাইল মাস্ট ক্লোজ করে ফেলতে হবে।

javascript
try {
  console.log("Opening the file...");
  // Read from file. May work or fail
} catch (error) {
  console.error("Error:", error.message);
} finally {
  console.log("Closing the file...");
}

// Output:
// Opening the file...
// Closing the file...

তবে সব সময় যে try-catch-finally তিনটা ব্লকই লিখতে হবে, এমন কোনো কথা নাই। প্রায় সময় দেখবি, শুধু try-catch লিখছে, ফাইনালি এর দরকার পড়ে নাই। তখন finally ব্লক লিখে না কেউ কেউ।

নিচের একটা ফাংশনে আমরা স্ট্রিংকে আপারকেইস করতে চাই। কেউ যদি স্ট্রিং আর্গুমেন্ট দিয়ে এই ফাংশনকে কল করে, তাহলে সে ঠিকমতো আউটপুট দিয়ে দিবে। আর কোনো কারণে স্ট্রিং না দিয়ে যদি কল বা অন্য কিছু দিয়ে কল করে, তাহলে catch-এর মধ্যে সেই এররকে ধরে ফেলতে পারবে। আর এইখানে শুধু try-catch ইউজ করা হইছে।

javascript
function getProperty(str) {
  try {
    return str.toUpperCase();
  } catch (error) {
    console.error("Error eaten by virus.");
  }
}

console.log(getProperty("My Name is Korona"));
console.log(getProperty());

// Output:
// MY NAME IS KORONA
// Error eaten by virus.

আবার শুধু try-finally-ও হতে পারে। নিচের মতো করে—

javascript
function performanceCleanUp() {
  try {
    console.log("Starting a process...");
  } finally {
    console.log("Cleaning up resources...");
  }
}

performanceCleanUp();

// Output:
// Starting a process...
// Cleaning up resources...

অনেক সময় error হওয়ার আগে আমরা চেক করে কনসোল লগ করে ফেলি। এইসব ক্ষেত্রে তুই চাইলে নিজেও একটা এরর বানিয়ে সেটাকে থ্রো করতে পারস। এইটাকে এরর থ্রো করা বলে।

javascript
function validateInput(input) {
  try {
    if (typeof input !== "string") {
      throw new Error("Input must be a string");
    }
    console.log("Valid input:", input);
  } catch (error) {
    if (error) {
      console.error("Custom Error:", error.message);
    } else {
      console.error("Unknown Error:", error.message);
    }
  }
}

validateInput("Hello");
validateInput(42);

// Output:
// Valid input: Hello
// Custom Error: Input must be a string

প্রয়োজনীয় জায়গায় try-catch-finally লেখা অনেক ইম্পরট্যান্ট।

Practice:

  1. একটা কোড লিখ, যেখানে JSON.parse ব্যবহার করে ডাটা পার্স করার চেষ্টা করবি। ধর, JSON ডাটা হলো {product: 'Date', price: 450}। তবে সার্ভার কোনো সময় ভুল ডাটা পাঠাতে পারে, যেমন "Data corrupted" লিখে স্ট্রিং ডাটা পাঠিয়ে দিল। যদি এরর হয়, সেটা catch ব্লকে হ্যান্ডেল করবি এবং কনসোলে ম্যাসেজ দিবি "Invalid JSON format"।

  2. validateInput নামে একটা ফাংশন বানা, যেটা ইউজারের ইনপুট চেক করবে। ইনপুট হতে হবে email address (যেমন test@example.com)। ইনপুট ই-মেইলে যদি @ চিহ্ন না থাকে, তাহলে custom error দেখাবে "Invalid email format"।

  3. JSON.parse ব্যবহার করে এমন একটা কোড ব্লক লিখে ফেল, যেখানে try ব্লকে ধর {role: 'CEO', weeklyHours: 1000} ডাটা আসছে। যদি ডাটা পার্স করতে পারিস এবং এরর খায়। যেটাই হোক না কেন, কনসোলে দেখাবি "Week is over"।

  4. stringOnlyParser ফাংশনে চেক কর। ইনপুট null বা empty string বা undefined হলে বলবি "Input must be a String."।

  5. try-catch-finally ব্যবহার করে এমন একটা কোড বানা, যেখানে ইউজারের একাউন্ট ডিলিট করার সিমুলেশন হবে। try ব্লকে বলবি "Deleting account", catch ব্লকে বলবি "Failed to delete account", আর finally ব্লকে বলবি "Account deletion attempt finished"।

19-6: Async আর Await-এর ভাজা মাছ

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

জাভাস্ক্রিপ্টও ওপরে ওপরে এমন ভাব করে যে, সে সিঙ্গেল থ্রেডে চলে। কিন্তু ভিতরে ভিতরে সে Asynchronous কোড ঠিকই হ্যান্ডেল করে ফেলে। আবার Asynchronous কোড হ্যান্ডেল করার পাশাপাশি async এবং await দিয়ে এমনভাবে কোড লেখার সিস্টেম নিয়ে আসছে, যাতে দেখলে মনে হয় নরমাল synchronous কোড। আসলে কিন্তু Asynchronous কোড।

আর আসল কথা হচ্ছে, এই async আর await ইউজ করলে কোড দেখা ও পড়া অনেক সহজ হয়ে ওঠে।

async কী?

async কি-ওয়ার্ডটি একটি ফাংশনকে asynchronous ফাংশনে রূপান্তরিত করে। এই async ফাংশন Promise রিটার্ন করে। ফাংশন লেখার সময় function কি-ওয়ার্ডের আগে মাস্ট async লিখতে হবে। আর অ্যারো ফাংশন হলে প্যারামিটারের আগে async লিখতে হবে। async না লিখেলে ফাংশনের ভিতরে await ইউজ করা যাবে না।

javascript
async function fetchData() {}
const fetchUserData = async () => {};

await কী?

শুধু async ফাংশনের মধ্যে await কি-ওয়ার্ডটি ব্যবহার করা যায়। যখন await একটি Promise দেখবে, তখন এটি সেই Promise শেষ হওয়ার জন্য অপেক্ষা করবে এবং তারপর প্রমিজের ফলাফল রিটার্ন করবে।

প্রথমে একটা সিম্পল উদাহরণ দিয়ে fetch-এর পর then-এর ভিতরে কলব্যাক ফাংশন দিয়ে ডাটা লোড করে ফেলি।

javascript
fetch("https://jsonplaceholder.typicode.com/users/1")
  .then((response) => response.json())
  .then((data) => {
    console.log(data);
  })
  .catch((error) => {
    console.error("Error:", error);
  });

async await

এখন যদি ওপরের fetch-কে async await-সহ ইউজ করি, তাহলে দেখতে নিচের মতো হবে। প্রথমে function কি-ওয়ার্ডের আগে async মাস্ট লিখতে হবে। তারপর একটা ফাংশন লিখবি নরমালভাবে। আর await সাধারণত try-catch-এর ভিতরে লেখা হয়, যাতে কোনো এরর খেলে সেটা catch ব্লক দিয়ে ক্যাচ করে হ্যান্ডেল করা যায়।

এরপর প্রথম লাইনে একটা url, তারপরের লাইনে একটা ভেরিয়েবল response, সেটার ডানপাশে await করতেছে। একটা fetch থেকে রেসপন্স রিটার্ন করার জন্য। নরমাল fetch ইউজ করলে এইটা then-এর ভিতরে পাওয়া যেত। এইখানে async await করার কারণে সরাসরি ভেরিয়েবলে পেয়ে যাচ্ছস। জিনিসটা কিন্তু সিম্পল। async চিন্তা করা লাগতেছে না। কলব্যাকের চিন্তা করা লাগতেছে না। জাস্ট fetch কলের আগে একটা await দিয়েই জিনিসটা সিম্পল করে ফেলছে।

তারপরের লাইনে আরেকটা ভেরিয়েবল। যেটার নাম data, আর সেটার ডানপাশে await আছে। অর্থাৎ অপেক্ষা করতেছে। response.json() ফিনিশ করার জন্য। যাতে ডাটাকে json দিয়ে জাভাস্ক্রিপ্ট অবজেক্টে কনভার্ট করতে পারে।

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

আর যদি কোডে কোনো এরর খায়, সেটা ধরার জন্য fetch কলের মেইন বিষয়টা try ব্লকের মধ্যে রাখছি। আর এরর খেলে সেটা catch ব্লকের ভিতরে হ্যান্ডেল করে ফেলব।

আর ওভারঅল fetch-এর বিষয়টা যেহেতু একটা ফাংশনের ভিতরে। তাই এই fetch কাজ করার জন্য আমাদের ফাংশনকে কল করে দিতে হবে। নচেৎ ডাটা কিন্তু লোড হবে না।

javascript
async function fetchData() {
  try {
    const url = "https://jsonplaceholder.typicode.com/users/1";
    const response = await fetch(url);
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error("Error:", error);
  }
}

fetchData();

ওপরে নরমাল একটা ফাংশনের মধ্যে আমরা async await ইউজ করছিলাম। যদি অ্যারো ফাংশনের মধ্যে async await ইউজ করতে যাস, সেইম সিস্টেম। জাস্ট async কি-ওয়ার্ডটা অ্যারো ফাংশনের প্যারামিটারের আগে লিখতে হবে। এইটুক খেয়াল করলেই হবে।

javascript
const fetchUserData = async () => {
  try {
    const response = await fetch(
      "https://jsonplaceholder.typicode.com/users/1",
    );
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error("Error:", error);
  }
};

fetchUserData();

একটু আগের নরমাল fetch আর এই async await কিন্তু একজাক্ট সেইম কাজই করে। নরমাল fetch আর কলব্যাক ফাংশন ইউজ করার কারণে কোড একটু প্যাঁচাপেঁচি লাগতে পারে। আর async-await ইউজ করে কোড লিখলে কোড অনেক গুছানো ফিল দেয়।

Callback hell

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

কাহিনি এমন হচ্ছে, প্রথমে ইউজারের ডাটা লোড করবে। ইউজার পেলে সেই ইউজারের আইডি দিয়ে তার সব পোস্টের ডাটা লোড করবে। তারপর তার সব পোস্ট পেলে প্রথম পোস্টের comments-এর ডাটা লোড করবে কমেন্ট আইডি দিয়ে। এইভাবে একটা পর একটা কলব্যাক দিয়ে দিয়ে ডাটা লোড করতে গেলে একটা কলব্যাক হেল হয়ে দাঁড়ায়।

javascript
// sample pseudo code. Will not run properly
fetch("user-url", (user) => {
  user.json((user) => {
    fetch(`post-url?userId=${user.id}`, (post) => {
      post.json((posts) => {
        fetch(`c-url?postId=${posts[0].id}`, (cmnts) => {
          comments.json((cmnts) => {
            // Do something with likes
          });
        });
      });
    });
  });
});

callback hell-এর আরেকটা নাম আছে, যেটাকে বলে Pyramid of Doom। কারণ, এইটা সাইড থেকে দেখলে মিশরের পিরামিডের মতো দেখতে। আর লজিক্যালভাবে দেখলে, দেখা যায় একটার ভিতরে আরেকটা, তার ভিতরের আরেকটা, এইরকম চলতেই আছে। এই রকম লেয়ার বাই লেয়ারের ভিতরে যাওয়াটাকে deep nesting কেউ কেউ বলে। এইসব ক্ষেত্রে কোনো সমস্যা হলে জীবন তেজপাতা হয়ে যায়। সমস্যা খুঁজে বের করা বা কোড ডিবাগ করা অনেক প্যারাময় হয়ে দাঁড়ায়। তখন async await ইউজ করলে কোড দেখতে, পড়তে, বুঝতে, এডিট করতে অনেক সহজ হয়ে দাঁড়ায়।

javascript
async function fetchData() {
  try {
    const userRes = await fetch("user-url");
    const user = await userRes.json();

    const postRes = await fetch(`post-url?userId=${user.id}`);
    const posts = await postRes.json();

    const cmntsRes = await fetch(`c-url?postId=${posts[0].id}`);
    const cmnts = await cmntsRes.json();

    // Do something with likes
  } catch (error) {
    console.error("Error:", error);
  }
}

fetchData();

এইবার বুঝতে পারছস। async await কাজ সেইম করে। কিন্তু কোডের সৌন্দর্য বাড়িয়ে দেয়।

Practice:

  1. async-await কেন ব্যবহার করা হয়?

  2. একটি async ফাংশন লিখে ফেল, আর এইটার নাম দিবি fetchUser। এই ফাংশনের ভিতরে এই url লিংক 'https://jsonplaceholder.typicode.com/users/2' থেকে ডাটা লোড করে ডাটাকে console লগ করবি। এ ছাড়া অবশ্যই try-catch ইউজ করবি।

  3. Callback hell বা Pyramid of Doom কী জিনিস? এইটা কখন হয়, আর এর সমাধান কী?

  4. 'https://jsonplaceholder.typicode.com/posts?userId=1' থেকে তার সব পোস্ট লোড কর। দুইভাবে কর। প্রথমবার callback স্টাইলে আর পরেরবার async-await দিয়ে।

  5. async-await দিয়ে একটা ফাংশন লিখ, যা 'https://jsonplaceholder.typicode.com/comments' থেকে কমেন্ট লোড করবে। অবশ্যই try-catch-finally ইউজ করবি এবং finally-তে একটা console.log দে, 'Request completed!'।

  6. Async-await দিয়ে এমন একটা ফাংশন লিখ, যেটা ইউজার ID প্যারামিটার হিসেবে নিবে। তারপর 'https://jsonplaceholder.typicode.com/users/ID' থেকে সেই ইউজারের ডাটা লোড করবে। ইউআরএলের মধ্যে লাস্টে টেমপ্লেট স্ট্রিং দিয়ে প্যারামিটার হিসেবে যেই ID দিবি, সেটা ডায়নামিকভাবে বসিয়ে দিবি। কোনো এরর হলে সেটাকে try-catch দিয়ে হ্যান্ডল করবি।

Released under the MIT License.