Asynch JS - $ এর শক্তি। বিলম্বিত

মসৃণ এবং প্রতিক্রিয়াশীল HTML5 অ্যাপ্লিকেশনগুলি তৈরি করার সবচেয়ে গুরুত্বপূর্ণ দিকগুলির মধ্যে একটি হল অ্যাপ্লিকেশনের সমস্ত বিভিন্ন অংশ যেমন ডেটা আনা, প্রক্রিয়াকরণ, অ্যানিমেশন এবং ব্যবহারকারী ইন্টারফেস উপাদানগুলির মধ্যে সিঙ্ক্রোনাইজেশন।

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

ব্রাউজার অ্যাসিঙ্ক্রোনাস API

সৌভাগ্যবশত, ব্রাউজারগুলি সাধারণভাবে ব্যবহৃত XHR (XMLHttpRequest বা 'AJAX') API, সেইসাথে IndexedDB, SQLite, HTML5 ওয়েব কর্মী, এবং HTML5 জিওলোকেশন এপিআইগুলির মতো কয়েকটি অ্যাসিঙ্ক্রোনাস API সরবরাহ করে। এমনকি কিছু DOM সম্পর্কিত অ্যাকশন অ্যাসিঙ্ক্রোনাসভাবে প্রকাশ করা হয়, যেমন CSS3 অ্যানিমেশন ট্রানজিশনএন্ড ইভেন্টের মাধ্যমে।

ব্রাউজারগুলি যেভাবে অ্যাসিঙ্ক্রোনাস প্রোগ্রামিংকে অ্যাপ্লিকেশন লজিকের কাছে প্রকাশ করে তা হল ঘটনা বা কলব্যাকের মাধ্যমে।
ইভেন্ট-ভিত্তিক অ্যাসিঙ্ক্রোনাস API-এ, ডেভেলপাররা একটি প্রদত্ত বস্তুর জন্য একটি ইভেন্ট হ্যান্ডলার নিবন্ধন করে (যেমন HTML এলিমেন্ট বা অন্যান্য DOM অবজেক্ট) এবং তারপর অ্যাকশনটি কল করে। ব্রাউজার সাধারণত একটি ভিন্ন থ্রেডে ক্রিয়া সম্পাদন করবে এবং উপযুক্ত হলে প্রধান থ্রেডে ইভেন্টটি ট্রিগার করবে।

উদাহরণস্বরূপ, XHR API ব্যবহার করে কোড, একটি ইভেন্ট ভিত্তিক অ্যাসিঙ্ক্রোনাস API, দেখতে এইরকম হবে:

// Create the XHR object to do GET to /data resource  
var xhr = new XMLHttpRequest();
xhr.open("GET","data",true);

// register the event handler
xhr.addEventListener('load',function(){
if(xhr.status === 200){
alert("We got data: " + xhr.response);
}
},false)

// perform the work
xhr.send();

CSS3 transitionEnd ইভেন্ট হল একটি ইভেন্ট ভিত্তিক অ্যাসিঙ্ক্রোনাস API এর আরেকটি উদাহরণ।

// get the html element with id 'flyingCar'  
var flyingCarElem = document.getElementById("flyingCar");

// register an event handler 
// ('transitionEnd' for FireFox, 'webkitTransitionEnd' for webkit) 
flyingCarElem.addEventListener("transitionEnd",function(){
// will be called when the transition has finished.
alert("The car arrived");
});

// add the CSS3 class that will trigger the animation
// Note: some browers delegate some transitions to the GPU , but 
//       developer does not and should not have to care about it.
flyingCarElemen.classList.add('makeItFly') 

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

উদাহরণস্বরূপ, HTML5 জিওলোকেশনের জন্য, কোডটি এইরকম দেখাচ্ছে:

// call and pass the function to callback when done.
navigator.geolocation.getCurrentPosition(function(position){  
        alert('Lat: ' + position.coords.latitude + ' ' +  
                'Lon: ' + position.coords.longitude);  
});  

এই ক্ষেত্রে, আমরা কেবল একটি পদ্ধতি কল করি এবং একটি ফাংশন পাস করি যা অনুরোধকৃত ফলাফলের সাথে ফিরে আসবে। এটি ব্রাউজারটিকে সিঙ্ক্রোনাস বা অ্যাসিঙ্ক্রোনাসভাবে এই কার্যকারিতা বাস্তবায়ন করতে এবং বাস্তবায়নের বিশদ নির্বিশেষে বিকাশকারীকে একটি একক API প্রদান করতে দেয়।

অ্যাসিঙ্ক্রোনাস-প্রস্তুত অ্যাপ্লিকেশন তৈরি করা

ব্রাউজারের অন্তর্নির্মিত অ্যাসিঙ্ক্রোনাস এপিআইগুলির বাইরে, ভাল আর্কিটেক্টেড অ্যাপ্লিকেশনগুলিকে তাদের নিম্ন স্তরের APIগুলিকে একটি অ্যাসিঙ্ক্রোনাস ফ্যাশনে প্রকাশ করা উচিত, বিশেষত যখন তারা কোনও ধরণের I/O বা গণনামূলক ভারী প্রক্রিয়াকরণ করে। উদাহরণস্বরূপ, ডেটা পেতে APIগুলি অ্যাসিঙ্ক্রোনাস হওয়া উচিত এবং এর মতো দেখতে হবে না:

// WRONG: this will make the UI freeze when getting the data  
var data = getData();
alert("We got data: " + data);

এই API ডিজাইনের জন্য getData() ব্লক করা প্রয়োজন, যা ডেটা আনা না হওয়া পর্যন্ত ইউজার ইন্টারফেস হিমায়িত করবে। যদি JavaScript প্রসঙ্গে ডেটা স্থানীয় হয় তবে এটি একটি সমস্যা নাও হতে পারে, তবে যদি ডেটা নেটওয়ার্ক থেকে বা এমনকি স্থানীয়ভাবে SQLite বা সূচক স্টোরে আনার প্রয়োজন হয় তবে এটি ব্যবহারকারীর অভিজ্ঞতার উপর নাটকীয় প্রভাব ফেলতে পারে।

সঠিক নকশাটি হল সমস্ত অ্যাপ্লিকেশন APIকে সক্রিয়ভাবে তৈরি করা যা প্রক্রিয়া করতে কিছু সময় নিতে পারে, শুরু থেকেই অ্যাসিঙ্ক্রোনাস কারণ রিট্রোফিটিং সিঙ্ক্রোনাস অ্যাপ্লিকেশন কোডকে অ্যাসিঙ্ক্রোনাস করা একটি কঠিন কাজ হতে পারে।

উদাহরণস্বরূপ সরলীকৃত getData() API এরকম কিছু হয়ে যাবে:

getData(function(data){
alert("We got data: " + data);
});

এই পদ্ধতির চমৎকার জিনিস হল যে এটি অ্যাপ্লিকেশন UI কোডকে শুরু থেকেই অ্যাসিঙ্ক্রোনাস-কেন্দ্রিক হতে বাধ্য করে এবং অন্তর্নিহিত APIগুলিকে সিদ্ধান্ত নিতে দেয় যে তাদের পরবর্তী পর্যায়ে অ্যাসিঙ্ক্রোনাস হতে হবে কি না।

মনে রাখবেন যে সমস্ত অ্যাপ্লিকেশন API এর প্রয়োজন নেই বা অ্যাসিঙ্ক্রোনাস হওয়া উচিত নয়। থাম্বের নিয়ম হল যে কোনো API যে কোনো ধরনের I/O বা ভারী প্রসেসিং করে (যে কোনো কিছু যা 15ms এর বেশি সময় নিতে পারে) শুরু থেকেই অ্যাসিঙ্ক্রোনাসভাবে প্রকাশ করা উচিত, এমনকি প্রথম প্রয়োগটি সিঙ্ক্রোনাস হলেও।

হ্যান্ডলিং ব্যর্থতা

অ্যাসিঙ্ক্রোনাস প্রোগ্রামিংয়ের একটি ক্যাচ হল যে ব্যর্থতাগুলি পরিচালনা করার ঐতিহ্যগত চেষ্টা/ক্যাচ পদ্ধতিটি সত্যিই আর কাজ করে না, কারণ ত্রুটি সাধারণত অন্য থ্রেডে ঘটে। ফলস্বরূপ, প্রক্রিয়াকরণের সময় কিছু ভুল হলে কলকারীকে অবহিত করার জন্য কলীর একটি কাঠামোগত উপায় থাকা দরকার।

একটি ইভেন্ট-ভিত্তিক অ্যাসিঙ্ক্রোনাস API-এ এটি প্রায়শই ইভেন্টটি গ্রহণ করার সময় ইভেন্ট বা অবজেক্টের অনুসন্ধানকারী অ্যাপ্লিকেশন কোড দ্বারা সম্পন্ন হয়। কলব্যাক ভিত্তিক অ্যাসিঙ্ক্রোনাস API-এর জন্য, সর্বোত্তম অনুশীলন হল একটি দ্বিতীয় আর্গুমেন্ট যা একটি ফাংশন নেয় যা যুক্তি হিসাবে উপযুক্ত ত্রুটির তথ্য সহ ব্যর্থতার ক্ষেত্রে কল করা হবে।

আমাদের getData কল এইরকম হবে:

// getData(successFunc,failFunc);  
getData(function(data){
alert("We got data: " + data);
}, function(ex){
alert("oops, some problem occured: " + ex);
});

এটিকে $.Deferred এর সাথে একত্রিত করা

উপরের কলব্যাক পদ্ধতির একটি সীমাবদ্ধতা হল যে এমনকি মাঝারিভাবে উন্নত সিঙ্ক্রোনাইজেশন লজিক লিখতে এটি সত্যিই কষ্টকর হয়ে উঠতে পারে।

উদাহরণস্বরূপ, যদি তৃতীয়টি করার আগে আপনাকে দুটি অ্যাসিঙ্ক্রোনাস API করার জন্য অপেক্ষা করতে হয়, কোড জটিলতা দ্রুত বাড়তে পারে।

// first do the get data.   
getData(function(data){
// then get the location
getLocation(function(location){
alert("we got data: " + data + " and location: " + location);
},function(ex){
alert("getLocation failed: "  + ex);
});
},function(ex){
alert("getData failed: " + ex);
});

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

সৌভাগ্যবশত, একটি অপেক্ষাকৃত পুরানো প্যাটার্ন আছে, যাকে বলা হয় Promises (জাভাতে ভবিষ্যতের মতই) এবং $. Deferred নামক jQuery কোরে একটি শক্তিশালী এবং আধুনিক বাস্তবায়ন যা অ্যাসিঙ্ক্রোনাস প্রোগ্রামিংয়ের একটি সহজ এবং শক্তিশালী সমাধান প্রদান করে।

এটিকে সহজ করার জন্য, প্রতিশ্রুতি প্যাটার্নটি সংজ্ঞায়িত করে যে অ্যাসিঙ্ক্রোনাস এপিআই একটি প্রতিশ্রুতি বস্তু প্রদান করে যা একটি "প্রতিশ্রুতি যে ফলাফলটি সংশ্লিষ্ট ডেটার সাথে সমাধান করা হবে।" রেজোলিউশন পেতে, কলার প্রতিশ্রুতি বস্তুটি পায় এবং একটি সম্পন্ন (সাফল্যফাঙ্ক(ডেটা)) কল করে যা প্রতিশ্রুতি বস্তুকে বলবে যে "ডেটা" সমাধান হয়ে গেলে এই সফলফাঙ্কে কল করতে।

সুতরাং, উপরের getData কল উদাহরণটি এরকম হয়:

// get the promise object for this API  
var dataPromise = getData();

// register a function to get called when the data is resolved
dataPromise.done(function(data){
alert("We got data: " + data);
});

// register the failure function
dataPromise.fail(function(ex){
alert("oops, some problem occured: " + ex);
});

// Note: we can have as many dataPromise.done(...) as we want. 
dataPromise.done(function(data){
alert("We asked it twice, we get it twice: " + data);
});

এখানে, আমরা প্রথমে dataPromise অবজেক্ট পাই এবং তারপরে একটি ফাংশন নিবন্ধন করার জন্য .done পদ্ধতিতে কল করি যখন আমরা ডেটা সমাধান হয়ে গেলে আবার কল করতে চাই। আমরা .fail পদ্ধতিকে শেষ ব্যর্থতা পরিচালনা করতেও কল করতে পারি। মনে রাখবেন যে আমরা যতগুলি প্রয়োজন ততগুলি .done বা .fail কল করতে পারি যেহেতু অন্তর্নিহিত প্রতিশ্রুতি বাস্তবায়ন (jQuery কোড) নিবন্ধন এবং কলব্যাকগুলি পরিচালনা করবে৷

এই প্যাটার্নের সাহায্যে, আরও উন্নত সিঙ্ক্রোনাইজেশন কোড প্রয়োগ করা তুলনামূলকভাবে সহজ, এবং jQuery ইতিমধ্যেই সবচেয়ে সাধারণ একটি $.when প্রদান করে।

উদাহরণস্বরূপ, উপরের নেস্টেড getData / getLocation কলব্যাকটি এরকম কিছু হয়ে যাবে:

// assuming both getData and getLocation return their respective Promise
var combinedPromise = $.when(getData(), getLocation())

// function will be called when both getData and getLocation resolve
combinePromise.done(function(data,location){
alert("We got data: " + dataResult + " and location: " + location);
});  

এবং এটির সৌন্দর্য হল যে jQuery.Deferred ডেভেলপারদের জন্য অ্যাসিঙ্ক্রোনাস ফাংশন বাস্তবায়ন করা খুব সহজ করে তোলে। উদাহরণস্বরূপ, getData দেখতে এরকম কিছু হতে পারে:

function getData(){
// 1) create the jQuery Deferred object that will be used
var deferred = $.Deferred();

// ---- AJAX Call ---- //
XMLHttpRequest xhr = new XMLHttpRequest();
xhr.open("GET","data",true);

// register the event handler
xhr.addEventListener('load',function(){
if(xhr.status === 200){
    // 3.1) RESOLVE the DEFERRED (this will trigger all the done()...)
    deferred.resolve(xhr.response);
}else{
    // 3.2) REJECT the DEFERRED (this will trigger all the fail()...)
    deferred.reject("HTTP error: " + xhr.status);
}
},false) 

// perform the work
xhr.send();
// Note: could and should have used jQuery.ajax. 
// Note: jQuery.ajax return Promise, but it is always a good idea to wrap it
//       with application semantic in another Deferred/Promise  
// ---- /AJAX Call ---- //

// 2) return the promise of this deferred
return deferred.promise();
}

সুতরাং, যখন getData() কল করা হয়, এটি প্রথমে একটি নতুন jQuery. Deferred অবজেক্ট (1) তৈরি করে এবং তারপর তার প্রতিশ্রুতি (2) ফেরত দেয় যাতে কলকারী তার সম্পন্ন এবং ব্যর্থ ফাংশনগুলি নিবন্ধন করতে পারে। তারপর, যখন XHR কলটি ফিরে আসে, এটি হয় স্থগিত (3.1) সমাধান করে বা এটিকে প্রত্যাখ্যান করে (3.2)। deferred.resolve করা সমস্ত সম্পন্ন(…) ফাংশন এবং অন্যান্য প্রতিশ্রুতি ফাংশনগুলিকে ট্রিগার করবে (যেমন, তারপর এবং পাইপ) এবং কলিং deferred.reject সমস্ত ব্যর্থ() ফাংশনকে কল করবে।

ব্যবহারের ক্ষেত্রে

এখানে কিছু ভাল ব্যবহারের ক্ষেত্রে রয়েছে যেখানে স্থগিত করা খুব দরকারী হতে পারে:

ডেটা অ্যাক্সেস: ডেটা অ্যাক্সেস API গুলিকে $. Deferred হিসাবে প্রকাশ করা প্রায়শই সঠিক নকশা। এটি দূরবর্তী ডেটার জন্য সুস্পষ্ট, কারণ সিঙ্ক্রোনাস রিমোট কলগুলি ব্যবহারকারীর অভিজ্ঞতা সম্পূর্ণরূপে নষ্ট করে দেয়, তবে স্থানীয় ডেটার ক্ষেত্রেও এটি সত্য কারণ প্রায়শই নিম্ন স্তরের APIগুলি (যেমন, SQLite এবং IndexedDB) অ্যাসিঙ্ক্রোনাস হয়৷ Deferred API's $.when এবং .pipe অত্যন্ত শক্তিশালী সিঙ্ক্রোনাইজ এবং চেইন অ্যাসিঙ্ক্রোনাস সাব-কোয়েরি।

UI অ্যানিমেশন: ট্রানজিশনএন্ড ইভেন্টের সাথে এক বা একাধিক অ্যানিমেশন অর্কেস্ট্রেট করা বেশ ক্লান্তিকর হতে পারে, বিশেষ করে যখন অ্যানিমেশনগুলি CSS3 অ্যানিমেশন এবং জাভাস্ক্রিপ্টের মিশ্রিত হয় (যেমন এটি প্রায়শই হয়)। স্থগিত হিসাবে অ্যানিমেশন ফাংশন মোড়ানো কোড জটিলতা উল্লেখযোগ্যভাবে হ্রাস করতে পারে এবং নমনীয়তা উন্নত করতে পারে। এমনকি একটি সাধারণ জেনেরিক র‍্যাপার ফাংশন যেমন cssAnimation(className) যা প্রমিস অবজেক্টকে ফিরিয়ে দেবে যা ট্রানজিশনএন্ডে সমাধান হয়ে যায় একটি দুর্দান্ত সাহায্য হতে পারে।

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

যেকোনো ব্রাউজার অ্যাসিঙ্ক্রোনাস API: স্বাভাবিককরণের উদ্দেশ্যে, ব্রাউজার API কলগুলিকে স্থগিত হিসাবে মোড়ানো একটি ভাল ধারণা। এটি আক্ষরিকভাবে প্রতিটি কোডের 4 থেকে 5 লাইন লাগে, তবে যেকোন অ্যাপ্লিকেশন কোডকে ব্যাপকভাবে সরল করবে। উপরের getData/getLocation ছদ্ম কোডে দেখানো হিসাবে, এটি অ্যাপ্লিকেশন কোডকে সমস্ত ধরণের API (ব্রাউজার, অ্যাপ্লিকেশনের নির্দিষ্টতা এবং যৌগ) জুড়ে একটি অ্যাসিঙ্ক্রোনাস মডেল থাকতে দেয়।

ক্যাশিং: এটি একটি পার্শ্ব সুবিধা, কিন্তু কিছু ক্ষেত্রে খুব দরকারী হতে পারে। যেহেতু প্রতিশ্রুতি APIs (যেমন, .done(…) এবং .fail(…)) অ্যাসিঙ্ক্রোনাস কল সঞ্চালিত হওয়ার আগে বা পরে কল করা যেতে পারে, ডিফার্ড অবজেক্ট একটি অ্যাসিঙ্ক্রোনাস কলের জন্য ক্যাশিং হ্যান্ডেল হিসাবে ব্যবহার করা যেতে পারে। উদাহরণস্বরূপ, একটি CacheManager শুধুমাত্র প্রদত্ত অনুরোধের জন্য স্থগিত ট্র্যাক রাখতে পারে, এবং যদি এটি অবৈধ না হয়ে থাকে তবে ম্যাচিং ডিফার্ডের প্রতিশ্রুতি ফেরত দিতে পারে। সৌন্দর্য হল যে কলকারীকে জানতে হবে না যে কলটি ইতিমধ্যে সমাধান করা হয়েছে বা সমাধানের প্রক্রিয়াধীন আছে, তার কলব্যাক ফাংশনটি ঠিক একইভাবে কল করা হবে।

উপসংহার

যদিও $. Deferred ধারণাটি সহজ, এটি একটি ভাল হ্যান্ডেল পেতে সময় নিতে পারে। যাইহোক, ব্রাউজার এনভায়রনমেন্টের প্রকৃতির কারণে, জাভাস্ক্রিপ্টে অ্যাসিঙ্ক্রোনাস প্রোগ্রামিং আয়ত্ত করা যেকোন গুরুতর HTML5 অ্যাপ্লিকেশন ডেভেলপারের জন্য আবশ্যক এবং প্রতিশ্রুতি প্যাটার্ন (এবং jQuery বাস্তবায়ন) অ্যাসিঙ্ক্রোনাস প্রোগ্রামিংকে নির্ভরযোগ্য এবং শক্তিশালী করার জন্য অসাধারণ টুল।