আজকে আমরা জাভাস্ক্রিপ্ট এর অদ্ভুত একটা ফিচার সম্পর্কে আলোচনা করবো যার নাম "প্রক্সি অবজেক্ট"। প্রথমেই আমাদের একটা হাই লেভেল ধারনা থাকা উচিত যে এই প্রক্সি অবজেক্ট বলতে আসলে কি বুঝানো হচ্ছে?
প্রক্সি অবজেক্ট মূলত কাস্টম অবজেক্ট স্ট্রাকচার যেটা সাধারণ জাভাস্ক্রিপ্ট অবজেক্ট এর মতই আচরন করে কিন্তু ডেভেলপারদের এক্সট্রা একটা সুপারপাওয়ার দিয়ে দেয় যার মাধ্যমে ডেভেলপাররা অবজেক্ট এর বিল্ট ইন অপারেশান গুলো কাস্টমাইজ করতে পারে। অপারেশন দিয়ে বুঝাচ্ছি অবজেক্ট এর get, set, deleteProperty, ownKeys, has, defineProperty, getOwnPropertyDescriptor ফাংশন বা মেথড গুলো।
প্রক্সি অবজেক্ট গুলো মূলত ৩টা উপাদান দিয়ে তৈরি - ১। টার্গেট - এটা হচ্ছে টার্গেট অবজেক্ট , অর্থাৎ এই অবজেক্ট এর উপর বেস করে একটা প্রক্সি তৈরি হবে যাকে আমরা ইচ্ছেমত কাস্টমাইজ করে নিতে পারবো। ২। হ্যান্ডলার - এটা মূলত একটা অবজেক্ট যেখানে আমরা সব ফাংশন বা মেথড গুলো ডিফাইন করবো। ৩। ট্র্যাপ - এটা মূলত হ্যান্ডলার অবজেক্টে ডিফাইন করা একেকটা ইন্টার্নাল মেথড।
যেমন - get, set, deleteProperty, ownKeys, has, defineProperty, getOwnPropertyDescriptor ফাংশন বা মেথড গুলো।
আমরা এই আর্টিকেলে প্রক্সি অবজেক্টের কোর নলেজ ক্লিয়ার করার জন্য যে বিষয় গুলো কভার করবো -
১। আমরা নিজেরা ম্যানুয়ালি একটা প্রক্সি অবজেক্ট ইমপ্লিমেন্ট করবো ।
২। আমরা জাভাস্ক্রিপ্ট এর বিল্ট ইন প্রক্সি অবজেক্ট কন্সট্রাকটর দিয়ে রিয়েল লাইফ একটা প্রব্লেম সলভ করা দেখবো.
তার আগে আমরা আরও একবার রিভিউ করে নি যে আমরা একচুয়ালি প্রক্সি অবজেক্ট এর কন্সট্রাকটর ফাংশন ইমপ্লিমেন্ট করতে যাচ্ছি ম্যানুয়ালি কোড করে এবং এই ফাংশন ২টা জিনিস নিবে , টার্গেট অবজেক্ট এবং হ্যান্ডলার অবজেক্ট। তারপর রিটার্ন করবে নতুন একটা অবজেক্ট যার ইন্টার্নাল অপারেশন গুলো আমাদের কাস্টম ভাবে ডিফাইন করা থাকবে। তাহলে চলুন কোড করা শুরু করে দেই -
function FakeProxy(target, handler) {
return {
get: handler.get
? (property) => handler.get(target, property)
: (property) => target[property],
set: handler.set
? handler.set(property, value)
: (property, value) => (target[property] = value),
};
}
const myObject = {};
const handler = {
get: (target, property) => {
return `Your name is ${target[property]}`;
},
};
const myProxy = new FakeProxy(myObject, handler);
myProxy.set("name", "Safin Ahmed");
console.log(myProxy.get("name"));
এখন আমরা লাইন বাই লাইন দেখতে থাকি যে আমাদের কোডটা কিভাবে কাজ করছে -
function FakeProxy(target, handler) {
return {
get: handler.get
? (property) => handler.get(target, property)
: (property) => target[property],
set: handler.set
? handler.set(property, value)
: (property, value) => (target[property] = value),
};
}
প্রথমে আমরা একটা কন্সট্রাকটর ফাংশন FakeProxy নামে ইমপ্লিমেন্ট করেছি যে ২টা আর্গুমেন্ট নিবে টার্গেট এবং হ্যান্ডলার ( টার্গেট হলো মুল অবজেক্ট যেটাকে প্রক্সি অবজেক্টে কনভার্ট করা হবে। এবং হ্যান্ডলার আরেকটা অবজেক্ট যেগুলো কাস্টম হ্যান্ডলার বা ট্রাপ্স নিয়ে থাকবে ) । এই কনস্ট্রাকটর ফাংশন রিটার্ন করবে একটা অবজেক্ট যেখানে আমরা কি হিসেবে প্রথম ইন্টার্নারল মেথড এর নাম দিয়েছি গেট এবং এখানে চেক করছি যে হ্যান্ডলার আর্গুমেন্ট এ ভ্যালিড হ্যান্ডলার অবজেক্ট দেওয়া হয়েছে কিনা, যদি হ্যান্ডলার অবজেক্ট থাকে তাহলে আমরা হ্যান্ডলার অবজেক্ট এর মধ্যে ডিফাইন করা গেট মেথড কল করছি এবং আর্গুমেন্ট হিসেবে টার্গেট অবজেক্ট আর প্রপার্টি দিয়ে দিচ্ছি। আর যদি ভ্যালিড হ্যান্ডলার অবজেক্ট দেওয়া না হয় তখন আমরা সাধারনভাবে যে প্রপার্টি চাওয়া হয়েছে সেই প্রপার্টি রিটার্ন করছি।
অনুরূপভাবে আমরা সেট মেথডটাও ইমপ্লিমেন্ট করে ফেললাম। এখন আমাদের FakeProxy কন্সট্রাকটর তৈরি হয়ে গিয়েছে।
const myObject = {};
তারপর আমরা একটা প্লেইন অবজেক্ট ডিফাইন করি myObject নামে।
const handler = {
get: (target, property) => {
return `Your name is ${target[property]}`;
},
};
তারপর আমাদের হ্যান্ডলার অবজেক্টটা তৈরি করতে হবে, কারন আমরা জানি , যে যেকোনো প্রক্সি অবজেক্টের ক্ষেত্রে ২টা উপাদান খুবই জুরুরী তার মধ্যে হ্যান্ডলার অবজেক্ট অন্যতম। আপাতত আমরা বুঝার জন্য গেট মেথডটা ইমপ্লিমেন্ট করবো।
গেট মেথড এর মধ্যে আমরা একটা টার্গেট অবজেক্ট পাবো আর একটা প্রপার্টি পাবো এবং এই মেথডটা রিটার্ন করবে একটা স্ট্রিং "Your name is ${target[property]}" এখানে আমরা প্রপার্টির ভ্যালুটা একটা স্ট্রিং এর মধ্যে পাঠাবো।
const myProxy = new FakeProxy(myObject, handler);
এবার আমরা ফাইনালি আমাদের প্রক্সি অবজেক্ট তৈরি করবো myProxy নামে । আর্গুমেন্ট হিসেবে দিয়ে দিবো আমাদের প্লেইন অবজেক্ট myObject এবং হ্যান্ডলার অবজেক্ট যেখানে আমরা শুধু গেট মেথড ইমপ্লিমেন্ট করে রেখেছি।
myProxy.set("name", "Safin Ahmed");
এখন আমাদের প্রক্সি অবজেক্ট myProxy এর set মেথড কল করে দিলাম। আর্গুমেন্ট হিসেবে দিলাম "name" এবং "Safin Ahmed" অর্থাৎ প্রথম আর্গুমেন্ট দিয়ে বুঝানো হচ্ছে আমি আমার অবজেক্টে "name" নামক প্রপার্টি এড করতে চাই এবং ভ্যালু হিসেবে দ্বিতীয় আর্গুমেন্ট "Safin Ahmed" রাখতে চাই।
এখন আমাদের প্রথমে শুরু করা প্লেইন অবজেক্ট আর প্লেইন নাই , একটা প্রপার্টি সেট হয়ে গিয়েছে নেম নামে।
console.log(myProxy.get("name"));
এখন আমার ইচ্ছে হলো আমি যে প্রপার্টিটা মাত্র সেট করলাম, সেটা ঠিকঠাক সেট হয়েছে কিনা তা যাচাই করার। এজন্য আমি পরের লাইনে কনসোল লগ করলাম {myProxy.get("name")} দিয়ে, তাহলে এটার মানে কি দাড়ালো ? আমি আমাদের প্রক্সি অবজেক্ট এর গেট মেথড কে কল করলাম এবং তাকে বললাম "name" নামক প্রপার্টির ভ্যালু আমাকে দিতে , সেই ভ্যালুটা আমি কনসলে প্রিন্ট করলাম।
ভ্যালুটা এরকম আসবে - Your name is Safin Ahmed
আচ্ছা তাহলে আমরা ফেক প্রক্সি অবজেক্ট ইমপ্লিমেন্ট করে ফেললাম কিন্তু আমি জানি আমরা সেটিসফাইড হতে পারি নি, নরমাল অবজেক্টে আমরা যেভাবে প্রপার্টির ভ্যালু এক্সেস করতে পারি ডট নোটেশন কিংবা এঙ্গেল ব্রাকেট দিয়ে অথবা ইকুয়াল সাইন দিয়ে যেভাবে নতুন ভ্যালু সেট করে দিতে পারি তার কোনটাই এখানে পেলাম না, সব ম্যানুয়ালি করতে হলো। এর সাথে আরেকটা প্রশ্নও মনে আসতে পারে এত আয়োজন এত ম্যানুয়াল কোড করে আমার কি লাভ কিংবা প্রোগ্রামার হিসেবে আমার কি সুবিধা?
এই প্রশ্নের জবাবে আমি বলবো ফ্লেক্সিবিলিটি এবং ফ্রিডম। এখানে আমি একটা অবজেক্ট থেকে একটা প্রপার্টি কিভাবে এক্সেস করবো, কিভাবে সেট করবো তা নিজের মত করে ইমপ্লিমেন্ট করতে পারবো। কাস্টম কোন চাহিদা থাকলে কিংবা লজিক থাকলে সেটাও আমি খুব সুন্দর করে ইমপ্লিমেন্ট করতে পারবো। যেমন - আমাদের লাস্ট উদাহরনে আমরা দেখলাম যে আমরা যখন আমাদের প্রক্সি অবজেক্টের "name" প্রপার্টির ভ্যালু চাইলাম তখন কিন্তু শুধু ভ্যালুই না বরং আমাদের মন মত একটা স্ট্রিং এর মধ্যে সেই ভ্যালুটা রিটার্ন করেছে। এখন চেষ্টা করে দেখেন তো সাধারণ জাভাস্ক্রিপ্ট অবজেক্টে এরকম উপায়ে ডেটা একসেস পসিবল হবে কিনা ?
এবার আমরা জাভাস্ক্রিপ্ট এর বিল্ট ইন প্রক্সি অবজেক্ট দিয়ে কিভাবে একটা রিয়েল ওয়ার্ল্ড প্রবলেম ক্যাশিং সলভ করা যায় সেটা দেখা যাক, আমি মূলত চাচ্ছি, যে আমার একটা অবজেক্ট থাকবে এবং সেখানে কি ভ্যলু পেয়ার হিসেবে কিছু ক্যারাকটারের ডেটা স্টোর হয়ে থাকবে , আমি যখন প্রথমবার একটা ক্যারাক্টার এর আইডি দিয়ে সেই ক্যারাক্টারের ডেটা এক্সেস করতে চাইবো তখন আগে অবজেক্ট চেক করা হবে যে ওই আইডি দিয়ে কোন প্রপার্টি অলরেডি আছে কিনা ? যদি না থাকে শুধুমাত্র তাহলেই এপিআই কল করা হবে, আর যদি ওই আইডি আগে থেকেই অবজেক্টে থাকে তাহলে আমরা সরাসরি ওই আইডির ক্যারাক্টারকে রিটার্ন করে দিবো, তাহলে চলুন আর দেরি না করে কোডে ঝাঁপিয়ে পড়ি -
async function fetchCharacterFromApi(id) {
try {
const character = await fetch(
`https://rickandmortyapi.com/api/character/${id}`
);
return character.json();
} catch (e) {
console.error(e);
throw e;
}
}
const characterCache = {}; // Target
const cacheHandler = {
get: async (target, id) => {
if (target[id]) {
return target[id];
} else {
const character = await fetchCharacterFromApi(id);
// characterCache[+id] = { ...character, time: new Date() };
return character;
}
},
has: (target, key) => {
return key in target;
},
set: (target, property, value) => {
console.log(`Setting property ${property} on cache`);
return (target[property] = { ...value, time: new Date() });
},
};
const getCharacter = new Proxy(characterCache, cacheHandler);
const character = await getCharacter[1];
console.log(`Has Character with id 1 in cache: ${1 in getCharacter}`);
if (!Boolean(1 in getCharacter)) {
getCharacter[1] = character;
}
const characterAgain = await getCharacter[1];
console.log({ characterAgain });
এবার উপরের সম্পুর্ন কোড আমরা লাইন বাই লাইন ভেঙ্গে দেখি যে কোনটা কিভাবে কাজ করছে -
প্রথমেই আমরা এপিআই কল করার একটা ফাংশন লিখেছি যার শুধুমাত্র একটাই কাজ এপিআই কল করা, এই ফাংশন আর্গুমেন্ট হিসেবে id নিচ্ছে এবং সেই id দিয়ে আমরা একটা ক্যারাক্টারের ডেটা এপিআই থেকে নিয়ে এসে রিটার্ন করছি। বলে রাখা ভালো , এই ফাংশন এর সাথে প্রক্সি অবজেক্ট এর কোন সম্পর্ক নেই।
// Api request to get a character by id
async function fetchCharacterFromApi(id) {
try {
const character = await fetch(
`https://rickandmortyapi.com/api/character/${id}`
);
return character.json();
} catch (e) {
console.error(e);
throw e;
}
}
এবার আমরা আমাদের টার্গেট অবজেক্ট characterCache ডিফাইন করলাম।
const characterCache = {}; // Target
তারপরের স্টেপ কি জানি ছিল? বলেন তো দেখি ? হ্যাঁ ঠিক বলেছেন আমাদের সেকেন্ড স্টেপ ছিল হ্যান্ডলার অবজেক্ট ডিফাইন করা। তাই আমরা একটা হ্যান্ডলার অবজেক্ট cacheHandler ডিফাইন করেছি, যার মধ্যে অবজেক্ট এর কিছু ইন্টার্নাল মেথড ডিফাইন করেছি।
// Api request to get a character by id
async function fetchCharacterFromApi(id) {
try {
const character = await fetch(
`https://rickandmortyapi.com/api/character/${id}`
);
return character.json();
} catch (e) {
console.error(e);
throw e;
}
}
const cacheHandler = {
get: async (target, id) => {
if (target[id]) {
return target[id];
} else {
const character = await fetchCharacterFromApi(id);
// characterCache[+id] = { ...character, time: new Date() };
return character;
}
},
has: (target, key) => {
return key in target;
},
set: (target, property, value) => {
console.log(`Setting property ${property} on cache`);
return (target[property] = { ...value, time: new Date() });
},
};
প্রথমেই আমরা get মেথড নিয়ে কাজ করেছি যার কাজ হলো অবজেক্টের কি বা প্রপার্টি এক্সেস করা। ফেইকপ্রক্সি এর হ্যান্ডলার এর মত এই রিয়েল প্রক্সি এর হ্যান্ডলার অবজেক্ট এর get মেথড এর লজিকও একইভাবে কাজ করবে , এখানে মেইন ডিফারেন্সটা শুধু এতটুকুই যে আমরা আগে চেক করে নিচ্ছি যে আমাদের টার্গেট অবজেক্টে আর্গুমেন্ট এর আইডি কি আগে থেকেই আছে? যদি আগে থেকেই আমাদের ডেটা থাকে তাহলে নতুন করে আর এপিআই কল করার প্রয়োজন নেই তাইনা? আর যদি না থাকে শুধুমাত্র তাহলেই আমরা এপিআই কল করবো এবং সেই ক্যারাকটারের ডেটা রিটার্ন করবো।
এখানে কমেন্ট করা একটা লাইন আছে, সেই লাইনটা কমেন্ট আউট করে দিলে কি কাজ করবে এবং কিভাবে করবে সেটা এক্সপ্লোর করার জন্য আপনাদের জন্য রেখে দিলাম। কমেন্টে অবশ্যই জানাবেন।
এরপর আরেকটা মেথড আমরা ইমপ্লিমেন্ট করবো যার নাম has , এই মেথড মূলত বুলিয়ান রিটার্ন করবে যে টার্গেট অবজেক্ট এর মধ্যে আর্গুমেন্ট এর কি আছে নাকি নাই?
সর্বশেষ আমরা সেট মেথড ইমপ্লিমেন্ট করছি যার কাজ হবে টার্গেট অবজেক্টে প্রপার্টি এবং ভ্যালু সেট করা। বুঝার সুবিধার্থে একটা কনসোল লগ করেছি যে আমাদের টার্গেট অবজেক্ট এ কোন প্রপার্টি বা কোন আইডি সেট করা হচ্ছে। আমরা শেষমেষ ভ্যালু সেট করছি একটা প্লেইন অবজেক্টে ভ্যালু স্প্রেড করে এবং একটা টাইম প্রপার্টি এড করে , যাতে আমরা বুঝতে পারি এই ডেটা ক্যাশ ডেটা , এপিআই কল করা হয় নি।
এভাবেই আমাদের শেষ হলো হ্যান্ডলার অবজেক্ট এর কাজ।
এবার আমরা শুরু করবো রিয়েল রিয়েল জাভাস্ক্রিপ্ট এর নিজস্ব প্রক্সি অবজেক্ট দিয়ে কাজ।
এখানে আমরা একটা প্রক্সি অবজেক্ট ডিফাইন করলাম - গেট ক্যারেক্টার নাম দিয়ে। তবে আগেরবার আমারা আমাদের কনস্ট্রাকটর ফাংশন FakeProxy বানিয়ে নিয়েছিলাম। কিন্তু এবার একই কাজ জাভাস্ক্রিপ্ট আমাদের করে দিচ্ছে তার বিল্ট ইন Proxy কনস্ট্রাকটর ফাংশন দিয়ে। আমাদের FakeProxy কনস্ট্রাকটর ফাংশন এর মতই জাভাস্ক্রিপ্ট এর বিল্ট ইন কনস্ট্রাকর ফাংশনেও ২টা জিনিস আমাদের দেওয়া লাগবে, একটা হলো টার্গেট অবজেক্ট এবং আরেকটা হলো হ্যান্ডলার অবজেক্ট।
// Api request to get a character by id
async function fetchCharacterFromApi(id) {
try {
const character = await fetch(
`https://rickandmortyapi.com/api/character/${id}`
);
return character.json();
} catch (e) {
console.error(e);
throw e;
}
}
const characterCache = {}; // Target
const cacheHandler = {
get: async (target, id) => {
if (target[id]) {
return target[id];
} else {
const character = await fetchCharacterFromApi(id);
// characterCache[+id] = { ...character, time: new Date() };
return character;
}
},
has: (target, key) => {
return key in target;
},
set: (target, property, value) => {
console.log(`Setting property ${property} on cache`);
return (target[property] = { ...value, time: new Date() });
},
};
const getCharacter = new Proxy(characterCache, cacheHandler);
এখন আমি চাচ্ছি যে প্রক্সি কনস্ট্রাকর আমাদের যে প্রক্সি অবজেক্ট দিলো getCharacter নামক ভ্যারিয়েবলে, আমি এখন সেই প্রক্সি অবজেক্টের মধ্যে 1 আইডি এর ক্যারেক্টার একসেস করতে চাচ্ছি। আমরা এর আগেরবার ম্যানুয়ালি একসেস করছিলাম get মেথড কল করে এবং সব আর্গুমেন্ট পাস করে কিন্তু এবার একদম স্বাভাবিক অবজেক্ট এর মত থার্ড ব্রাকেট নোটেশন ইউস করে আমরা get করছি। অর্থাৎ, getCharacter[1] এই লাইনটি বিহাইন্ড দা সিন আমাদের হ্যান্ডলার অবজেক্টের get মেথডকেই কল করছে এবং আর্গুমেন্ট হিসেবে id = 1 পাস করে দিচ্ছে। এই লাইনের আগে await বসানো হয়েছে কারন ক্যাশে ডেটা খুজে না পেলে এপিআই থেকে ফেচ করে আনা হবে তাই।
এখন আমার কাছে ক্যারাকটার এর ডেটা আছে character ভ্যারিয়েবলে যা আমরা গেট করলাম তাই না? এখন আমাকে চেক করতে হবে যে এই আইডি টা কি অলরেডি অবজেক্টে এর ভিতর আছে নাকি নাই? সেটাও আমরা নরমাল অবজেক্ট এর মত in অপারেটর দিয়ে চেক করতে পারি যে কোন কি এই অবজেক্টে এক্সিস্ট করে কি না? এবং এই in অপারেটর আমাদের হ্যান্ডলার অবজেক্টের has মেথডকে হিট করবে। এই in অপারেটর একটা বুলিয়ান রিটার্ন করবে, তাই আমরা চেক করছি যে বুলিয়ানটা যদি ফলসি ভ্যালু হয়ে থাকে তাহলে আমরা বুঝবো যে আমাদের ক্যাশে ওই ক্যারাক্টারের ডেটা নেই। তার মানে উপরে আমরা যে ডেটা গেট করলাম, সেটা এখন আমরা ক্যাশে সেট করে দিতে পারি। তাই আমরা যেটা করলাম, আমরা if ব্লক এর ভিতর ঢুকে getCharacter[1] = character দিয়ে বুঝালাম যে ভাই, getCharacter নামক অবজেক্টের 1 নামের প্রপার্টির ভ্যালু সেট করে দাও character নামক ভ্যারিয়েবল এর ডেটাকে।
// Api request to get a character by id
async function fetchCharacterFromApi(id) {
try {
const character = await fetch(
`https://rickandmortyapi.com/api/character/${id}`
);
return character.json();
} catch (e) {
console.error(e);
throw e;
}
}
const characterCache = {}; // Target
const cacheHandler = {
get: async (target, id) => {
if (target[id]) {
return target[id];
} else {
const character = await fetchCharacterFromApi(id);
// characterCache[+id] = { ...character, time: new Date() };
return character;
}
},
has: (target, key) => {
return key in target;
},
set: (target, property, value) => {
console.log(`Setting property ${property} on cache`);
return (target[property] = { ...value, time: new Date() });
},
};
const getCharacter = new Proxy(characterCache, cacheHandler);
const character = await getCharacter[1];
console.log(`Has Character with id 1 in cache: ${1 in getCharacter}`);
if (!Boolean(1 in getCharacter)) {
getCharacter[1] = character;
}
const characterAgain = await getCharacter[1];
console.log({ characterAgain });
তার মানে এখন আমাদের ক্যাশে id = 1 এর ডেটা আছে। এবার যদি আমি আবার 1 নাম্বার আইডি গেট করি এবং সেটার ভ্যালু characterAgain নামক ভ্যারিয়েবলে রাখি তারপর সেই ভ্যারিয়েবল এর মান কোনসলে প্রিন্ট করি তাহলে দেখতে পাবো যে এবার ক্যারেক্টারের ডেটা ঠিকঠাক আসছে কিন্তু শেষের দিকে একটা টাইমস্ট্যাম্প আছে। মনে আছে আমরা কি করেছিলাম? আমরা কিন্তু আমাদের প্রক্সি অবজেক্টের সেটার মেথডে বলে দিয়েছিলাম যেকোন ভ্যালু সেট করার সময় একটা প্লেইন অবজেক্টে সব ভ্যালু স্প্রেড করে, একটা টাইমস্ট্যাম্প বসিয়ে দিতে। তার মানে আমাদের ডেটা কিন্তু এখন ক্যাশ থেকে আসছে , এপিআই কল করা হয় নি।
সো এটাই ছিলো আমাদের জাভাস্ক্রিপ্ট প্রক্সি অবজেক্ট নিয়ে হাল্কা খেলাধুলা আশা করি আপনাদের ভালো লেগেছে, খুব শীঘ্রই দেখা হবে নতুন আর্টিকেল নিয়ে। হ্যাপি নিউ ইয়ার এভ্রিওয়ান।