আপনাকে বলা হয়েছে "প্রধান থ্রেড ব্লক করবেন না" এবং "আপনার দীর্ঘ কাজগুলি ভেঙে ফেলুন", কিন্তু এই জিনিসগুলি করার অর্থ কী?
প্রকাশিত: 30 সেপ্টেম্বর, 2022, শেষ আপডেট: ডিসেম্বর 19, 2024
জাভাস্ক্রিপ্ট অ্যাপ্লিকেশানগুলিকে দ্রুত রাখার জন্য সাধারণ উপদেশগুলি নিম্নোক্ত পরামর্শগুলিকে ফোটাতে থাকে:
- "প্রধান থ্রেড ব্লক করবেন না।"
- "আপনার দীর্ঘ কাজগুলি ভেঙে দিন।"
এটা মহান উপদেশ, কিন্তু এটা কি কাজ জড়িত? কম জাভাস্ক্রিপ্ট শিপিং ভাল, কিন্তু এটি কি স্বয়ংক্রিয়ভাবে আরও প্রতিক্রিয়াশীল ব্যবহারকারী ইন্টারফেসের সাথে সমান হয়? হতে পারে, কিন্তু হয়তো না।
জাভাস্ক্রিপ্টে কাজগুলি কীভাবে অপ্টিমাইজ করা যায় তা বোঝার জন্য, আপনাকে প্রথমে জানতে হবে কাজগুলি কী এবং ব্রাউজার কীভাবে সেগুলি পরিচালনা করে৷
একটি টাস্ক কি?
একটি টাস্ক হল ব্রাউজার যে কোন আলাদা কাজ করে। সেই কাজের মধ্যে রয়েছে রেন্ডারিং, HTML এবং CSS পার্স করা, JavaScript চালানো এবং অন্যান্য ধরনের কাজ যার উপর আপনার সরাসরি নিয়ন্ত্রণ নাও থাকতে পারে। এই সবের মধ্যে, আপনি যে জাভাস্ক্রিপ্ট লেখেন তা সম্ভবত কাজের সবচেয়ে বড় উৎস।
জাভাস্ক্রিপ্টের সাথে যুক্ত কার্যগুলি কয়েকটি উপায়ে কার্যক্ষমতাকে প্রভাবিত করে:
- যখন একটি ব্রাউজার স্টার্টআপের সময় একটি জাভাস্ক্রিপ্ট ফাইল ডাউনলোড করে, তখন এটি সেই জাভাস্ক্রিপ্ট পার্স এবং কম্পাইল করার জন্য কাজগুলিকে সারিবদ্ধ করে যাতে এটি পরে চালানো যায়।
- পৃষ্ঠার জীবনের অন্যান্য সময়ে, কাজগুলি সারিবদ্ধ থাকে যখন জাভাস্ক্রিপ্ট কাজ করে যেমন ইভেন্ট হ্যান্ডলারের মাধ্যমে ইন্টারঅ্যাকশনের প্রতিক্রিয়া, জাভাস্ক্রিপ্ট-চালিত অ্যানিমেশন এবং ব্যাকগ্রাউন্ড কার্যকলাপ যেমন বিশ্লেষণ সংগ্রহ।
এই সমস্ত স্টাফ- ওয়েব কর্মী এবং অনুরূপ APIগুলি বাদ দিয়ে-প্রধান থ্রেডে ঘটে।
মূল থ্রেড কি?
প্রধান থ্রেড হল যেখানে বেশিরভাগ কাজ ব্রাউজারে চলে এবং যেখানে আপনার লেখা প্রায় সমস্ত জাভাস্ক্রিপ্ট চালানো হয়।
প্রধান থ্রেড একটি সময়ে শুধুমাত্র একটি কাজ প্রক্রিয়া করতে পারে. 50 মিলিসেকেন্ডের বেশি সময় লাগে এমন যেকোনো কাজ একটি দীর্ঘ কাজ । 50 মিলিসেকেন্ডের বেশি কাজের জন্য, টাস্কের মোট সময় বিয়োগ 50 মিলিসেকেন্ড টাস্কের ব্লকিং সময়কাল হিসাবে পরিচিত।
ব্রাউজার যে কোনো দৈর্ঘ্যের একটি টাস্ক চলাকালীন ঘটতে পারস্পরিক ক্রিয়াগুলিকে অবরুদ্ধ করে, তবে এটি ব্যবহারকারীর পক্ষে উপলব্ধি করা যায় না যতক্ষণ পর্যন্ত কাজগুলি খুব বেশি সময় ধরে না চলে। যখন অনেক দীর্ঘ কাজ থাকা অবস্থায় একজন ব্যবহারকারী একটি পৃষ্ঠার সাথে ইন্টারঅ্যাক্ট করার চেষ্টা করে, তবে, ব্যবহারকারী ইন্টারফেসটি প্রতিক্রিয়াহীন বোধ করবে, এবং সম্ভবত এমনকি ভেঙে যেতে পারে যদি মূল থ্রেডটি খুব দীর্ঘ সময়ের জন্য অবরুদ্ধ থাকে।
মূল থ্রেডটিকে খুব বেশি সময় অবরুদ্ধ করা থেকে আটকাতে, আপনি একটি দীর্ঘ টাস্ককে কয়েকটি ছোট ছোট করে ভাগ করতে পারেন।
এটি গুরুত্বপূর্ণ কারণ যখন কাজগুলি বিচ্ছিন্ন হয়ে যায়, ব্রাউজারটি উচ্চ-অগ্রাধিকারমূলক কাজে অনেক তাড়াতাড়ি সাড়া দিতে পারে — ব্যবহারকারীর মিথস্ক্রিয়া সহ। পরবর্তীতে, অবশিষ্ট কাজগুলি শেষ হওয়ার দিকে চলে যায়, আপনি প্রাথমিকভাবে যে কাজটি সারিবদ্ধ করেছিলেন তা নিশ্চিত করে।
পূর্ববর্তী চিত্রের শীর্ষে, ব্যবহারকারীর মিথস্ক্রিয়া দ্বারা সারিবদ্ধ একটি ইভেন্ট হ্যান্ডলারকে এটি শুরু করার আগে একটি দীর্ঘ টাস্কের জন্য অপেক্ষা করতে হয়েছিল, এটি মিথস্ক্রিয়াটি ঘটতে বিলম্বিত করে। এই পরিস্থিতিতে, ব্যবহারকারী ল্যাগ লক্ষ্য করতে পারে. নীচে, ইভেন্ট হ্যান্ডলার শীঘ্রই চালানো শুরু করতে পারে, এবং মিথস্ক্রিয়া তাত্ক্ষণিকভাবে অনুভূত হতে পারে৷
এখন যেহেতু আপনি জানেন যে কেন কাজগুলি বিচ্ছিন্ন করা গুরুত্বপূর্ণ, আপনি জাভাস্ক্রিপ্টে এটি কীভাবে করবেন তা শিখতে পারেন।
টাস্ক ম্যানেজমেন্ট কৌশল
সফ্টওয়্যার আর্কিটেকচারে একটি সাধারণ উপদেশ হল আপনার কাজকে ছোট ফাংশনে বিভক্ত করা:
function saveSettings () {
validateForm();
showSpinner();
saveToDatabase();
updateUI();
sendAnalytics();
}
এই উদাহরণে, saveSettings()
নামে একটি ফাংশন আছে যা একটি ফর্ম যাচাই করতে, একটি স্পিনার দেখাতে, অ্যাপ্লিকেশন ব্যাকএন্ডে ডেটা পাঠাতে, ব্যবহারকারীর ইন্টারফেস আপডেট করতে এবং বিশ্লেষণ পাঠাতে পাঁচটি ফাংশন কল করে।
ধারণাগতভাবে, saveSettings()
ভালোভাবে আর্কিটেক্ট করা হয়েছে। আপনি যদি এই ফাংশনগুলির মধ্যে একটি ডিবাগ করতে চান তবে প্রতিটি ফাংশন কী করে তা নির্ধারণ করতে আপনি প্রজেক্ট ট্রিটি অতিক্রম করতে পারেন। এই ধরনের কাজ ব্রেক আপ প্রকল্পগুলি নেভিগেট এবং বজায় রাখা সহজ করে তোলে।
এখানে একটি সম্ভাব্য সমস্যা, যদিও, জাভাস্ক্রিপ্ট এই প্রতিটি ফাংশনকে আলাদা কাজ হিসাবে চালায় না কারণ সেগুলি saveSettings()
ফাংশনের মধ্যে কার্যকর করা হয়। এর মানে হল যে পাঁচটি ফাংশন একটি কাজ হিসাবে চলবে।
সর্বোত্তম ক্ষেত্রে, এমনকি শুধুমাত্র এই ফাংশনগুলির মধ্যে একটি কাজটির মোট দৈর্ঘ্যে 50 মিলিসেকেন্ড বা তার বেশি অবদান রাখতে পারে। সবচেয়ে খারাপ ক্ষেত্রে, এই কাজগুলির মধ্যে আরও অনেক বেশি সময় চলতে পারে-বিশেষ করে সংস্থান-সীমাবদ্ধ ডিভাইসগুলিতে।
এই ক্ষেত্রে, saveSettings()
একটি ব্যবহারকারীর ক্লিক দ্বারা ট্রিগার হয়, এবং যেহেতু পুরো ফাংশনটি চালানো শেষ না হওয়া পর্যন্ত ব্রাউজার একটি প্রতিক্রিয়া দেখাতে সক্ষম হয় না, এই দীর্ঘ টাস্কের ফলাফল হল একটি ধীর এবং প্রতিক্রিয়াশীল UI, এবং হবে নেক্সট পেইন্ট (INP) এর সাথে একটি দুর্বল মিথস্ক্রিয়া হিসাবে পরিমাপ করা হয়।
ম্যানুয়ালি কোড এক্সিকিউশন পিছিয়ে দিন
নিম্ন-অগ্রাধিকারমূলক কাজগুলির আগে গুরুত্বপূর্ণ ব্যবহারকারী-মুখী কাজগুলি এবং UI প্রতিক্রিয়াগুলি ঘটতে পারে তা নিশ্চিত করতে, আপনি ব্রাউজারকে আরও গুরুত্বপূর্ণ কাজগুলি চালানোর সুযোগ দেওয়ার জন্য আপনার কাজকে সংক্ষিপ্তভাবে বাধা দিয়ে মূল থ্রেডে উঠতে পারেন৷
একটি পদ্ধতি ডেভেলপাররা কাজগুলিকে ছোট করে ভাগ করার জন্য ব্যবহার করেছেন setTimeout()
। এই কৌশলটির সাহায্যে, আপনি ফাংশনটি setTimeout()
এ পাস করুন। এটি একটি পৃথক টাস্কে কলব্যাকের সম্পাদন স্থগিত করে, এমনকি যদি আপনি 0
এর সময়সীমা নির্দিষ্ট করেন।
function saveSettings () {
// Do critical work that is user-visible:
validateForm();
showSpinner();
updateUI();
// Defer work that isn't user-visible to a separate task:
setTimeout(() => {
saveToDatabase();
sendAnalytics();
}, 0);
}
এটি yielding নামে পরিচিত, এবং এটি ক্রমাগতভাবে চালানোর প্রয়োজন এমন একটি সিরিজের ফাংশনের জন্য সেরা কাজ করে।
যাইহোক, আপনার কোড সবসময় এই ভাবে সংগঠিত নাও হতে পারে। উদাহরণস্বরূপ, আপনার কাছে প্রচুর পরিমাণে ডেটা থাকতে পারে যা একটি লুপে প্রসেস করা দরকার, এবং যদি অনেকগুলি পুনরাবৃত্তি থাকে তবে সেই কাজটি খুব দীর্ঘ সময় নিতে পারে।
function processData () {
for (const item of largeDataArray) {
// Process the individual item here.
}
}
এখানে setTimeout()
ব্যবহার করা ডেভেলপার এরগনোমিক্সের কারণে সমস্যাযুক্ত, এবং নেস্টেড setTimeout()
s এর পাঁচ রাউন্ডের পরে, ব্রাউজার প্রতিটি অতিরিক্ত setTimeout()
জন্য ন্যূনতম 5 মিলিসেকেন্ড বিলম্ব চাপানো শুরু করবে।
setTimeout
আরও একটি অপূর্ণতা রয়েছে যখন এটি ফলন আসে: যখন আপনি setTimeout
ব্যবহার করে পরবর্তী টাস্কে চালানোর জন্য ডিফারিং কোডের মাধ্যমে মূল থ্রেডে যোগ দেন, তখন সেই টাস্কটি সারির শেষে যোগ করা হয়। যদি অন্য কাজগুলি অপেক্ষা করে থাকে তবে সেগুলি আপনার বিলম্বিত কোডের আগে চলবে৷
একটি উত্সর্গীকৃত ফলন API: scheduler.yield()
scheduler.yield()
একটি API বিশেষভাবে ব্রাউজারে প্রধান থ্রেড প্রদানের জন্য ডিজাইন করা হয়েছে।
এটি ভাষা-স্তরের সিনট্যাক্স বা একটি বিশেষ গঠন নয়; scheduler.yield()
এমন একটি ফাংশন যা একটি Promise
প্রদান করে যা ভবিষ্যতের টাস্কে সমাধান করা হবে। সেই Promise
সমাধান হওয়ার পরে চালানোর জন্য চেইন করা যেকোন কোড (হয় একটি স্পষ্ট .then()
চেইনে বা এটি একটি async ফাংশনে await
পরে) তারপর সেই ভবিষ্যতের টাস্কে চলবে৷
অনুশীলনে: একটি await scheduler.yield()
সন্নিবেশ করান এবং ফাংশনটি সেই সময়ে এক্সিকিউশনকে বিরতি দেবে এবং মূল থ্রেডে যোগ দেবে। বাকি ফাংশন-এর এক্সিকিউশন-যাকে ফাংশনের ধারাবাহিকতা বলা হয়-একটি নতুন ইভেন্ট-লুপ টাস্কে চালানোর জন্য নির্ধারিত হবে। যখন সেই কাজটি শুরু হবে, প্রতীক্ষিত প্রতিশ্রুতিটি সমাধান করা হবে, এবং ফাংশনটি যেখানে ছেড়েছিল সেখানে কার্যকর করা অব্যাহত থাকবে।
async function saveSettings () {
// Do critical work that is user-visible:
validateForm();
showSpinner();
updateUI();
// Yield to the main thread:
await scheduler.yield()
// Work that isn't user-visible, continued in a separate task:
saveToDatabase();
sendAnalytics();
}
অন্যান্য ফলন পদ্ধতির তুলনায় scheduler.yield()
এর আসল সুবিধা হল, এটির ধারাবাহিকতা অগ্রাধিকার দেওয়া হয়, যার মানে হল যে আপনি যদি একটি টাস্কের মাঝখানে ফলন করেন, তাহলে বর্তমান টাস্কের ধারাবাহিকতা অন্য কোন অনুরূপ কাজের আগে চলবে। শুরু
এটি অন্যান্য টাস্ক সোর্স থেকে কোডকে আপনার কোডের এক্সিকিউশনের ক্রমকে বাধাগ্রস্ত করা থেকে এড়ায়, যেমন থার্ড-পার্টি স্ক্রিপ্টের কাজ।
ক্রস ব্রাউজার সমর্থন
scheduler.yield()
এখনও সব ব্রাউজারে সমর্থিত নয়, তাই একটি ফলব্যাক প্রয়োজন।
একটি সমাধান হল আপনার বিল্ডে scheduler-polyfill
ড্রপ করা, এবং তারপরে scheduler.yield()
সরাসরি ব্যবহার করা যেতে পারে; পলিফিল অন্যান্য টাস্ক-শিডিউলিং ফাংশনগুলিতে ফিরে যাওয়া পরিচালনা করবে যাতে এটি ব্রাউজার জুড়ে একইভাবে কাজ করে।
বিকল্পভাবে, একটি কম পরিশীলিত সংস্করণ কয়েক লাইনে লেখা যেতে পারে, যদি scheduler.yield()
উপলভ্য না থাকে তবে একটি প্রতিশ্রুতিতে মোড়ানো setTimeout
ব্যবহার করে।
function yieldToMain () {
if (globalThis.scheduler?.yield) {
return scheduler.yield();
}
// Fall back to yielding with setTimeout.
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
যদিও scheduler.yield()
সমর্থন ছাড়া ব্রাউজারগুলি অগ্রাধিকারমূলক ধারাবাহিকতা পাবে না, তবুও তারা ব্রাউজারকে প্রতিক্রিয়াশীল থাকার জন্য ফল দেবে।
অবশেষে, এমন কিছু ক্ষেত্রে হতে পারে যেখানে আপনার কোডটি মূল থ্রেডে যোগ দিতে পারে না যদি এর ধারাবাহিকতাকে অগ্রাধিকার দেওয়া না হয় (উদাহরণস্বরূপ, একটি পরিচিত-ব্যস্ত পৃষ্ঠা যেখানে কিছু সময়ের জন্য কাজ সম্পূর্ণ না করার ঝুঁকি থাকে)। সেক্ষেত্রে, scheduler.yield()
এক ধরনের প্রগতিশীল বর্ধন হিসাবে বিবেচনা করা যেতে পারে: ব্রাউজারে ফলন যেখানে scheduler.yield()
পাওয়া যায়, অন্যথায় চালিয়ে যান।
একটি সুবিধাজনক ওয়ান-লাইনারে একটি একক মাইক্রোটাস্কের জন্য অপেক্ষা করা বৈশিষ্ট্য সনাক্তকরণ এবং ফিরে আসা উভয়ের মাধ্যমে এটি করা যেতে পারে:
// Yield to the main thread if scheduler.yield() is available.
await globalThis.scheduler?.yield?.();
scheduler.yield()
এর সাথে দীর্ঘদিন ধরে চলমান কাজ ভেঙে দিন
scheduler.yield()
ব্যবহার করার এই পদ্ধতিগুলির যে কোনও একটি ব্যবহার করার সুবিধা হল যে আপনি যে কোনও async
ফাংশনে এটির জন্য await
করতে পারেন।
উদাহরণস্বরূপ, যদি আপনার কাছে চালানোর জন্য কাজগুলির একটি অ্যারে থাকে যা প্রায়শই একটি দীর্ঘ টাস্ক যোগ করে, আপনি টাস্কটি ভাঙতে ফলন সন্নিবেশ করতে পারেন।
async function runJobs(jobQueue) {
for (const job of jobQueue) {
// Run the job:
job();
// Yield to the main thread:
await yieldToMain();
}
}
runJobs()
এর ধারাবাহিকতাকে অগ্রাধিকার দেওয়া হবে, কিন্তু তারপরও ব্যবহারকারীর ইনপুটকে দৃশ্যমানভাবে সাড়া দেওয়ার মতো উচ্চ-অগ্রাধিকারমূলক কাজকে মঞ্জুরি দেয়, কাজগুলির সম্ভাব্য দীর্ঘ তালিকা শেষ হওয়ার জন্য অপেক্ষা করতে হবে না।
যাইহোক, এটি ফলন একটি দক্ষ ব্যবহার নয়. scheduler.yield()
দ্রুত এবং দক্ষ, কিন্তু এর কিছু ওভারহেড আছে। যদি jobQueue
কিছু কাজ খুব সংক্ষিপ্ত হয়, তাহলে ওভারহেডটি প্রকৃত কাজ সম্পাদনের চেয়ে ফলন এবং পুনরায় শুরু করার জন্য ব্যয় করা আরও বেশি সময় যোগ করতে পারে।
একটি পদ্ধতি হল কাজগুলি ব্যাচ করা, শুধুমাত্র তাদের মধ্যে ফলন যদি শেষ ফলনের পর থেকে যথেষ্ট দীর্ঘ হয়। একটি সাধারণ সময়সীমা হল 50 মিলিসেকেন্ড কাজগুলিকে দীর্ঘ টাস্কে পরিণত করা থেকে বিরত রাখার চেষ্টা করার জন্য, তবে এটি প্রতিক্রিয়াশীলতা এবং কাজের সারি সম্পূর্ণ করার জন্য সময়ের মধ্যে ট্রেডঅফ হিসাবে সামঞ্জস্য করা যেতে পারে।
async function runJobs(jobQueue, deadline=50) {
let lastYield = performance.now();
for (const job of jobQueue) {
// Run the job:
job();
// If it's been longer than the deadline, yield to the main thread:
if (performance.now() - lastYield > deadline) {
await yieldToMain();
lastYield = performance.now();
}
}
}
ফলাফল হল যে কাজগুলি বিচ্ছিন্ন হয়ে গেছে যাতে দৌড়াতে খুব বেশি সময় লাগে না, কিন্তু রানার শুধুমাত্র প্রতি 50 মিলিসেকেন্ডে মূল থ্রেডে ফল দেয়।
isInputPending()
ব্যবহার করবেন না
isInputPending()
API একজন ব্যবহারকারী একটি পৃষ্ঠার সাথে ইন্টারঅ্যাক্ট করার চেষ্টা করেছে কিনা তা পরীক্ষা করার একটি উপায় প্রদান করে এবং যদি একটি ইনপুট মুলতুবি থাকে তবেই ফল দেয়।
এটি জাভাস্ক্রিপ্টকে চালিয়ে যেতে দেয় যদি কোনও ইনপুট মুলতুবি না থাকে, ফলন না করে এবং টাস্ক কিউয়ের পিছনে শেষ হয়। এর ফলে প্রভাবশালী পারফরম্যান্সের উন্নতি হতে পারে, যেমনটি Intent to Ship- এ বিশদভাবে বলা হয়েছে, এমন সাইটগুলির জন্য যা অন্যথায় মূল থ্রেডে ফিরে আসতে পারে না।
যাইহোক, সেই API চালু হওয়ার পর থেকে, ফলন সম্পর্কে আমাদের বোঝা বেড়েছে, বিশেষ করে INP প্রবর্তনের মাধ্যমে। আমরা আর এই API ব্যবহার করার পরামর্শ দিই না , এবং পরিবর্তে বিভিন্ন কারণে ইনপুট মুলতুবি থাকুক বা না থাকুক না কেন ফলন দেওয়ার সুপারিশ করি:
- একজন ব্যবহারকারী কিছু পরিস্থিতিতে ইন্টারঅ্যাক্ট করা সত্ত্বেও
isInputPending()
ভুলভাবেfalse
ফেরত দিতে পারে। - ইনপুট একমাত্র ক্ষেত্রে নয় যেখানে কার্যগুলি পাওয়া উচিত। অ্যানিমেশন এবং অন্যান্য নিয়মিত ব্যবহারকারী ইন্টারফেস আপডেট একটি প্রতিক্রিয়াশীল ওয়েব পৃষ্ঠা প্রদানের জন্য সমানভাবে গুরুত্বপূর্ণ হতে পারে।
- এরপর থেকে আরও ব্যাপক ফলনকারী API চালু করা হয়েছে যা
scheduler.postTask()
এবংscheduler.yield()
মতো উদ্বেগের সমাধান করে।
উপসংহার
কাজগুলি পরিচালনা করা চ্যালেঞ্জিং, তবে এটি করা নিশ্চিত করে যে আপনার পৃষ্ঠা ব্যবহারকারীর ইন্টারঅ্যাকশনগুলিতে আরও দ্রুত সাড়া দেয়। কাজগুলি পরিচালনা এবং অগ্রাধিকার দেওয়ার জন্য কোনও একক পরামর্শ নেই, বরং বিভিন্ন কৌশল রয়েছে। পুনরাবৃত্ত করার জন্য, এইগুলি হল প্রধান জিনিসগুলি যা আপনি কাজগুলি পরিচালনা করার সময় বিবেচনা করতে চান:
- গুরুত্বপূর্ণ, ব্যবহারকারী-মুখী কাজগুলির জন্য মূল থ্রেডে যোগ দিন।
- এর্গোনমিকভাবে ফলন এবং অগ্রাধিকারযুক্ত ধারাবাহিকতা পেতে
scheduler.yield()
(একটি ক্রস-ব্রাউজার ফলব্যাক সহ) ব্যবহার করুন - অবশেষে, আপনার ফাংশনে যতটা সম্ভব কম কাজ করুন।
scheduler.yield()
, এর সুস্পষ্ট টাস্ক-শিডিউলিং আপেক্ষিক scheduler.postTask()
, এবং টাস্ক অগ্রাধিকার সম্পর্কে আরও জানতে, অগ্রাধিকারযুক্ত টাস্ক শিডিউলিং API ডক্স দেখুন।
এই টুলগুলির মধ্যে এক বা একাধিক দিয়ে, আপনি আপনার অ্যাপ্লিকেশনে কাজটি গঠন করতে সক্ষম হবেন যাতে এটি ব্যবহারকারীর চাহিদাগুলিকে অগ্রাধিকার দেয়, এবং নিশ্চিত করে যে কম সমালোচনামূলক কাজ এখনও সম্পন্ন হয়। এটি একটি ভাল ব্যবহারকারীর অভিজ্ঞতা তৈরি করতে যাচ্ছে যা আরও প্রতিক্রিয়াশীল এবং ব্যবহার করা আরও উপভোগ্য।
ফিলিপ ওয়ালটনকে বিশেষ ধন্যবাদ তার এই নির্দেশিকাটির প্রযুক্তিগত পরীক্ষা করার জন্য।
থাম্বনেইল ছবি আনস্প্ল্যাশ থেকে নেওয়া, আমিরালি মিরহাশেমিয়ানের সৌজন্যে।
,আপনাকে বলা হয়েছে "প্রধান থ্রেড ব্লক করবেন না" এবং "আপনার দীর্ঘ কাজগুলি ভেঙে ফেলুন", কিন্তু এই জিনিসগুলি করার অর্থ কী?
প্রকাশিত: 30 সেপ্টেম্বর, 2022, শেষ আপডেট: ডিসেম্বর 19, 2024
জাভাস্ক্রিপ্ট অ্যাপ্লিকেশানগুলিকে দ্রুত রাখার জন্য সাধারণ উপদেশগুলি নিম্নোক্ত পরামর্শগুলিকে ফোটাতে থাকে:
- "প্রধান থ্রেড ব্লক করবেন না।"
- "আপনার দীর্ঘ কাজগুলি ভেঙে দিন।"
এটা মহান উপদেশ, কিন্তু এটা কি কাজ জড়িত? কম জাভাস্ক্রিপ্ট শিপিং ভাল, কিন্তু এটি কি স্বয়ংক্রিয়ভাবে আরও প্রতিক্রিয়াশীল ব্যবহারকারী ইন্টারফেসের সাথে সমান হয়? হতে পারে, কিন্তু হয়তো না।
জাভাস্ক্রিপ্টে কাজগুলি কীভাবে অপ্টিমাইজ করা যায় তা বোঝার জন্য, আপনাকে প্রথমে জানতে হবে কাজগুলি কী এবং ব্রাউজার কীভাবে সেগুলি পরিচালনা করে৷
একটি টাস্ক কি?
একটি টাস্ক হল ব্রাউজার যে কোন আলাদা কাজ করে। সেই কাজের মধ্যে রয়েছে রেন্ডারিং, HTML এবং CSS পার্স করা, JavaScript চালানো এবং অন্যান্য ধরনের কাজ যার উপর আপনার সরাসরি নিয়ন্ত্রণ নাও থাকতে পারে। এই সবের মধ্যে, আপনি যে জাভাস্ক্রিপ্ট লেখেন তা সম্ভবত কাজের সবচেয়ে বড় উৎস।
জাভাস্ক্রিপ্টের সাথে যুক্ত কার্যগুলি কয়েকটি উপায়ে কার্যক্ষমতাকে প্রভাবিত করে:
- যখন একটি ব্রাউজার স্টার্টআপের সময় একটি জাভাস্ক্রিপ্ট ফাইল ডাউনলোড করে, তখন এটি সেই জাভাস্ক্রিপ্ট পার্স এবং কম্পাইল করার জন্য কাজগুলিকে সারিবদ্ধ করে যাতে এটি পরে চালানো যায়।
- পৃষ্ঠার জীবনের অন্যান্য সময়ে, কাজগুলি সারিবদ্ধ থাকে যখন জাভাস্ক্রিপ্ট কাজ করে যেমন ইভেন্ট হ্যান্ডলারের মাধ্যমে ইন্টারঅ্যাকশনের প্রতিক্রিয়া, জাভাস্ক্রিপ্ট-চালিত অ্যানিমেশন এবং ব্যাকগ্রাউন্ড কার্যকলাপ যেমন বিশ্লেষণ সংগ্রহ।
এই সমস্ত স্টাফ- ওয়েব কর্মী এবং অনুরূপ APIগুলি বাদ দিয়ে-প্রধান থ্রেডে ঘটে।
মূল থ্রেড কি?
প্রধান থ্রেড হল যেখানে বেশিরভাগ কাজ ব্রাউজারে চলে এবং যেখানে আপনার লেখা প্রায় সমস্ত জাভাস্ক্রিপ্ট চালানো হয়।
প্রধান থ্রেড একটি সময়ে শুধুমাত্র একটি কাজ প্রক্রিয়া করতে পারে. 50 মিলিসেকেন্ডের বেশি সময় লাগে এমন যেকোনো কাজ একটি দীর্ঘ কাজ । 50 মিলিসেকেন্ডের বেশি কাজের জন্য, টাস্কের মোট সময় বিয়োগ 50 মিলিসেকেন্ড টাস্কের ব্লকিং সময়কাল হিসাবে পরিচিত।
ব্রাউজার যে কোনো দৈর্ঘ্যের একটি টাস্ক চলাকালীন ঘটতে পারস্পরিক ক্রিয়াগুলিকে অবরুদ্ধ করে, তবে এটি ব্যবহারকারীর পক্ষে উপলব্ধি করা যায় না যতক্ষণ পর্যন্ত কাজগুলি খুব বেশি সময় ধরে না চলে। যখন অনেক দীর্ঘ কাজ থাকা অবস্থায় একজন ব্যবহারকারী একটি পৃষ্ঠার সাথে ইন্টারঅ্যাক্ট করার চেষ্টা করে, তবে, ব্যবহারকারী ইন্টারফেসটি প্রতিক্রিয়াহীন বোধ করবে, এবং সম্ভবত এমনকি ভেঙে যেতে পারে যদি মূল থ্রেডটি খুব দীর্ঘ সময়ের জন্য অবরুদ্ধ থাকে।
মূল থ্রেডটিকে খুব বেশি সময় অবরুদ্ধ করা থেকে আটকাতে, আপনি একটি দীর্ঘ টাস্ককে কয়েকটি ছোট ছোট করে ভাগ করতে পারেন।
এটি গুরুত্বপূর্ণ কারণ যখন কাজগুলি বিচ্ছিন্ন হয়ে যায়, ব্রাউজারটি উচ্চ-অগ্রাধিকারমূলক কাজে অনেক তাড়াতাড়ি সাড়া দিতে পারে — ব্যবহারকারীর মিথস্ক্রিয়া সহ। পরবর্তীতে, অবশিষ্ট কাজগুলি শেষ হওয়ার দিকে চলে যায়, আপনি প্রাথমিকভাবে যে কাজটি সারিবদ্ধ করেছিলেন তা নিশ্চিত করে।
পূর্ববর্তী চিত্রের শীর্ষে, ব্যবহারকারীর মিথস্ক্রিয়া দ্বারা সারিবদ্ধ একটি ইভেন্ট হ্যান্ডলারকে এটি শুরু করার আগে একটি দীর্ঘ টাস্কের জন্য অপেক্ষা করতে হয়েছিল, এটি মিথস্ক্রিয়াটি ঘটতে বিলম্বিত করে। এই পরিস্থিতিতে, ব্যবহারকারী ল্যাগ লক্ষ্য করতে পারে. নীচে, ইভেন্ট হ্যান্ডলার শীঘ্রই চালানো শুরু করতে পারে, এবং মিথস্ক্রিয়া তাত্ক্ষণিক অনুভূত হতে পারে৷
এখন যেহেতু আপনি জানেন যে কেন কাজগুলি বিচ্ছিন্ন করা গুরুত্বপূর্ণ, আপনি জাভাস্ক্রিপ্টে এটি কীভাবে করবেন তা শিখতে পারেন।
টাস্ক ম্যানেজমেন্ট কৌশল
সফ্টওয়্যার আর্কিটেকচারে একটি সাধারণ উপদেশ হল আপনার কাজকে ছোট ফাংশনে বিভক্ত করা:
function saveSettings () {
validateForm();
showSpinner();
saveToDatabase();
updateUI();
sendAnalytics();
}
এই উদাহরণে, saveSettings()
নামে একটি ফাংশন আছে যা একটি ফর্ম যাচাই করতে, একটি স্পিনার দেখাতে, অ্যাপ্লিকেশন ব্যাকএন্ডে ডেটা পাঠাতে, ব্যবহারকারীর ইন্টারফেস আপডেট করতে এবং বিশ্লেষণ পাঠাতে পাঁচটি ফাংশন কল করে।
ধারণাগতভাবে, saveSettings()
ভালোভাবে আর্কিটেক্ট করা হয়েছে। আপনি যদি এই ফাংশনগুলির মধ্যে একটি ডিবাগ করতে চান তবে প্রতিটি ফাংশন কী করে তা নির্ধারণ করতে আপনি প্রজেক্ট ট্রিটি অতিক্রম করতে পারেন। এই ধরনের কাজ ব্রেক আপ প্রকল্পগুলি নেভিগেট এবং বজায় রাখা সহজ করে তোলে।
এখানে একটি সম্ভাব্য সমস্যা, যদিও, জাভাস্ক্রিপ্ট এই প্রতিটি ফাংশনকে আলাদা কাজ হিসাবে চালায় না কারণ সেগুলি saveSettings()
ফাংশনের মধ্যে কার্যকর করা হয়। এর মানে হল যে পাঁচটি ফাংশন একটি কাজ হিসাবে চলবে।
সর্বোত্তম ক্ষেত্রে, এমনকি শুধুমাত্র এই ফাংশনগুলির মধ্যে একটি কাজটির মোট দৈর্ঘ্যে 50 মিলিসেকেন্ড বা তার বেশি অবদান রাখতে পারে। সবচেয়ে খারাপ ক্ষেত্রে, এই কাজগুলির মধ্যে আরও অনেক বেশি সময় চলতে পারে-বিশেষ করে সংস্থান-সীমাবদ্ধ ডিভাইসগুলিতে।
এই ক্ষেত্রে, saveSettings()
একটি ব্যবহারকারীর ক্লিক দ্বারা ট্রিগার হয়, এবং যেহেতু পুরো ফাংশনটি চালানো শেষ না হওয়া পর্যন্ত ব্রাউজার একটি প্রতিক্রিয়া দেখাতে সক্ষম হয় না, এই দীর্ঘ টাস্কের ফলাফল হল একটি ধীর এবং প্রতিক্রিয়াশীল UI, এবং হবে নেক্সট পেইন্ট (INP) এর সাথে একটি দুর্বল মিথস্ক্রিয়া হিসাবে পরিমাপ করা হয়।
ম্যানুয়ালি কোড এক্সিকিউশন পিছিয়ে দিন
নিম্ন-অগ্রাধিকারমূলক কাজগুলির আগে গুরুত্বপূর্ণ ব্যবহারকারী-মুখী কাজগুলি এবং UI প্রতিক্রিয়াগুলি ঘটতে পারে তা নিশ্চিত করতে, আপনি ব্রাউজারকে আরও গুরুত্বপূর্ণ কাজগুলি চালানোর সুযোগ দেওয়ার জন্য আপনার কাজকে সংক্ষিপ্তভাবে বাধা দিয়ে মূল থ্রেডে উঠতে পারেন৷
একটি পদ্ধতি ডেভেলপাররা কাজগুলিকে ছোট করে ভাগ করার জন্য ব্যবহার করেছেন setTimeout()
। এই কৌশলটির সাহায্যে, আপনি ফাংশনটি setTimeout()
এ পাস করুন। এটি একটি পৃথক টাস্কে কলব্যাকের সম্পাদন স্থগিত করে, এমনকি যদি আপনি 0
এর সময়সীমা নির্দিষ্ট করেন।
function saveSettings () {
// Do critical work that is user-visible:
validateForm();
showSpinner();
updateUI();
// Defer work that isn't user-visible to a separate task:
setTimeout(() => {
saveToDatabase();
sendAnalytics();
}, 0);
}
এটি yielding নামে পরিচিত, এবং এটি ক্রমাগতভাবে চালানোর প্রয়োজন এমন একটি সিরিজের ফাংশনের জন্য সেরা কাজ করে।
যাইহোক, আপনার কোড সবসময় এই ভাবে সংগঠিত নাও হতে পারে। উদাহরণস্বরূপ, আপনার কাছে প্রচুর পরিমাণে ডেটা থাকতে পারে যা একটি লুপে প্রসেস করা দরকার, এবং যদি অনেকগুলি পুনরাবৃত্তি থাকে তবে সেই কাজটি খুব দীর্ঘ সময় নিতে পারে।
function processData () {
for (const item of largeDataArray) {
// Process the individual item here.
}
}
এখানে setTimeout()
ব্যবহার করা ডেভেলপার এরগনোমিক্সের কারণে সমস্যাযুক্ত, এবং নেস্টেড setTimeout()
s এর পাঁচ রাউন্ডের পরে, ব্রাউজার প্রতিটি অতিরিক্ত setTimeout()
জন্য ন্যূনতম 5 মিলিসেকেন্ড বিলম্ব চাপানো শুরু করবে।
setTimeout
আরও একটি অপূর্ণতা রয়েছে যখন এটি ফলন আসে: যখন আপনি setTimeout
ব্যবহার করে পরবর্তী টাস্কে চালানোর জন্য ডিফারিং কোডের মাধ্যমে মূল থ্রেডে যোগ দেন, তখন সেই টাস্কটি সারির শেষে যোগ করা হয়। যদি অন্য কাজগুলি অপেক্ষা করে থাকে তবে সেগুলি আপনার বিলম্বিত কোডের আগে চলবে৷
একটি উত্সর্গীকৃত ফলন API: scheduler.yield()
scheduler.yield()
একটি API বিশেষভাবে ব্রাউজারে প্রধান থ্রেড প্রদানের জন্য ডিজাইন করা হয়েছে।
এটি ভাষা-স্তরের সিনট্যাক্স বা একটি বিশেষ গঠন নয়; scheduler.yield()
এমন একটি ফাংশন যা একটি Promise
প্রদান করে যা ভবিষ্যতের টাস্কে সমাধান করা হবে। সেই Promise
সমাধান হওয়ার পরে চালানোর জন্য চেইন করা যেকোন কোড (হয় একটি স্পষ্ট .then()
চেইনে বা এটি একটি async ফাংশনে await
পরে) তারপর সেই ভবিষ্যতের টাস্কে চলবে৷
অনুশীলনে: একটি await scheduler.yield()
সন্নিবেশ করান এবং ফাংশনটি সেই সময়ে এক্সিকিউশনকে বিরতি দেবে এবং মূল থ্রেডে যোগ দেবে। বাকি ফাংশন-এর এক্সিকিউশন-যাকে ফাংশনের ধারাবাহিকতা বলা হয়-একটি নতুন ইভেন্ট-লুপ টাস্কে চালানোর জন্য নির্ধারিত হবে। যখন সেই কাজটি শুরু হবে, প্রতীক্ষিত প্রতিশ্রুতিটি সমাধান করা হবে, এবং ফাংশনটি যেখানে ছেড়েছিল সেখানে কার্যকর করা অব্যাহত থাকবে।
async function saveSettings () {
// Do critical work that is user-visible:
validateForm();
showSpinner();
updateUI();
// Yield to the main thread:
await scheduler.yield()
// Work that isn't user-visible, continued in a separate task:
saveToDatabase();
sendAnalytics();
}
অন্যান্য ফলন পদ্ধতির তুলনায় scheduler.yield()
এর আসল সুবিধা হল, এটির ধারাবাহিকতা অগ্রাধিকার দেওয়া হয়, যার মানে হল যে আপনি যদি একটি টাস্কের মাঝখানে ফলন করেন, তাহলে বর্তমান টাস্কের ধারাবাহিকতা অন্য কোন অনুরূপ কাজের আগে চলবে। শুরু
এটি অন্যান্য টাস্ক সোর্স থেকে কোডকে আপনার কোডের এক্সিকিউশনের ক্রমকে বাধাগ্রস্ত করা থেকে এড়ায়, যেমন থার্ড-পার্টি স্ক্রিপ্টের কাজ।
ক্রস ব্রাউজার সমর্থন
scheduler.yield()
এখনও সব ব্রাউজারে সমর্থিত নয়, তাই একটি ফলব্যাক প্রয়োজন।
একটি সমাধান হল আপনার বিল্ডে scheduler-polyfill
ড্রপ করা, এবং তারপরে scheduler.yield()
সরাসরি ব্যবহার করা যেতে পারে; পলিফিল অন্যান্য টাস্ক-শিডিউলিং ফাংশনগুলিতে ফিরে যাওয়া পরিচালনা করবে যাতে এটি ব্রাউজার জুড়ে একইভাবে কাজ করে।
বিকল্পভাবে, একটি কম পরিশীলিত সংস্করণ কয়েক লাইনে লেখা যেতে পারে, যদি scheduler.yield()
উপলভ্য না থাকে তবে একটি প্রতিশ্রুতিতে মোড়ানো setTimeout
ব্যবহার করে।
function yieldToMain () {
if (globalThis.scheduler?.yield) {
return scheduler.yield();
}
// Fall back to yielding with setTimeout.
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
যদিও scheduler.yield()
সমর্থন ছাড়া ব্রাউজারগুলি অগ্রাধিকারমূলক ধারাবাহিকতা পাবে না, তবুও তারা ব্রাউজারকে প্রতিক্রিয়াশীল থাকার জন্য ফল দেবে।
অবশেষে, এমন কিছু ক্ষেত্রে হতে পারে যেখানে আপনার কোডটি মূল থ্রেডে যোগ দিতে পারে না যদি এর ধারাবাহিকতাকে অগ্রাধিকার দেওয়া না হয় (উদাহরণস্বরূপ, একটি পরিচিত-ব্যস্ত পৃষ্ঠা যেখানে কিছু সময়ের জন্য কাজ সম্পূর্ণ না করার ঝুঁকি থাকে)। সেক্ষেত্রে, scheduler.yield()
এক ধরনের প্রগতিশীল বর্ধন হিসাবে বিবেচনা করা যেতে পারে: ব্রাউজারে ফলন যেখানে scheduler.yield()
পাওয়া যায়, অন্যথায় চালিয়ে যান।
একটি সুবিধাজনক ওয়ান-লাইনারে একটি একক মাইক্রোটাস্কের জন্য অপেক্ষা করা বৈশিষ্ট্য সনাক্তকরণ এবং ফিরে আসা উভয়ের মাধ্যমে এটি করা যেতে পারে:
// Yield to the main thread if scheduler.yield() is available.
await globalThis.scheduler?.yield?.();
scheduler.yield()
এর সাথে দীর্ঘদিন ধরে চলমান কাজ ভেঙে দিন
scheduler.yield()
ব্যবহার করার এই পদ্ধতিগুলির যে কোনও একটি ব্যবহার করার সুবিধা হল যে আপনি যে কোনও async
ফাংশনে এটির জন্য await
করতে পারেন।
উদাহরণস্বরূপ, যদি আপনার কাছে চালানোর জন্য কাজগুলির একটি অ্যারে থাকে যা প্রায়শই একটি দীর্ঘ টাস্ক যোগ করে, আপনি টাস্কটি ভাঙতে ফলন সন্নিবেশ করতে পারেন।
async function runJobs(jobQueue) {
for (const job of jobQueue) {
// Run the job:
job();
// Yield to the main thread:
await yieldToMain();
}
}
runJobs()
এর ধারাবাহিকতাকে অগ্রাধিকার দেওয়া হবে, কিন্তু তারপরও ব্যবহারকারীর ইনপুটকে দৃশ্যমানভাবে সাড়া দেওয়ার মতো উচ্চ-অগ্রাধিকারমূলক কাজকে মঞ্জুরি দেয়, কাজগুলির সম্ভাব্য দীর্ঘ তালিকা শেষ হওয়ার জন্য অপেক্ষা করতে হবে না।
যাইহোক, এটি ফলন একটি দক্ষ ব্যবহার নয়. scheduler.yield()
দ্রুত এবং দক্ষ, কিন্তু এর কিছু ওভারহেড আছে। যদি jobQueue
কিছু কাজ খুব সংক্ষিপ্ত হয়, তাহলে ওভারহেডটি প্রকৃত কাজ সম্পাদনের চেয়ে ফলন এবং পুনরায় শুরু করার জন্য ব্যয় করা আরও বেশি সময় যোগ করতে পারে।
একটি পদ্ধতি হল কাজগুলি ব্যাচ করা, শুধুমাত্র তাদের মধ্যে ফলন যদি শেষ ফলনের পর থেকে যথেষ্ট দীর্ঘ হয়। একটি সাধারণ সময়সীমা হল 50 মিলিসেকেন্ড কাজগুলিকে দীর্ঘ টাস্কে পরিণত করা থেকে বিরত রাখার চেষ্টা করার জন্য, তবে এটি প্রতিক্রিয়াশীলতা এবং কাজের সারি সম্পূর্ণ করার জন্য সময়ের মধ্যে ট্রেডঅফ হিসাবে সামঞ্জস্য করা যেতে পারে।
async function runJobs(jobQueue, deadline=50) {
let lastYield = performance.now();
for (const job of jobQueue) {
// Run the job:
job();
// If it's been longer than the deadline, yield to the main thread:
if (performance.now() - lastYield > deadline) {
await yieldToMain();
lastYield = performance.now();
}
}
}
ফলাফল হল যে কাজগুলি বিচ্ছিন্ন হয়ে গেছে যাতে দৌড়াতে খুব বেশি সময় লাগে না, কিন্তু রানার শুধুমাত্র প্রতি 50 মিলিসেকেন্ডে মূল থ্রেডে ফল দেয়।
isInputPending()
ব্যবহার করবেন না
isInputPending()
API একজন ব্যবহারকারী একটি পৃষ্ঠার সাথে ইন্টারঅ্যাক্ট করার চেষ্টা করেছে কিনা তা পরীক্ষা করার একটি উপায় প্রদান করে এবং যদি একটি ইনপুট মুলতুবি থাকে তবেই ফল দেয়।
এটি জাভাস্ক্রিপ্টকে চালিয়ে যেতে দেয় যদি কোনও ইনপুট মুলতুবি না থাকে, ফলন না করে এবং টাস্ক কিউয়ের পিছনে শেষ হয়। এর ফলে প্রভাবশালী পারফরম্যান্সের উন্নতি হতে পারে, যেমনটি Intent to Ship- এ বিশদভাবে বলা হয়েছে, এমন সাইটগুলির জন্য যা অন্যথায় মূল থ্রেডে ফিরে আসতে পারে না।
যাইহোক, সেই API চালু হওয়ার পর থেকে, ফলন সম্পর্কে আমাদের বোঝা বেড়েছে, বিশেষ করে INP প্রবর্তনের মাধ্যমে। আমরা আর এই API ব্যবহার করার পরামর্শ দিই না , এবং পরিবর্তে বিভিন্ন কারণে ইনপুট মুলতুবি থাকুক বা না থাকুক না কেন ফলন দেওয়ার সুপারিশ করি:
- একজন ব্যবহারকারী কিছু পরিস্থিতিতে ইন্টারঅ্যাক্ট করা সত্ত্বেও
isInputPending()
ভুলভাবেfalse
ফেরত দিতে পারে। - ইনপুট একমাত্র ক্ষেত্রে নয় যেখানে কার্যগুলি পাওয়া উচিত। অ্যানিমেশন এবং অন্যান্য নিয়মিত ব্যবহারকারী ইন্টারফেস আপডেট একটি প্রতিক্রিয়াশীল ওয়েব পৃষ্ঠা প্রদানের জন্য সমানভাবে গুরুত্বপূর্ণ হতে পারে।
- এরপর থেকে আরও ব্যাপক ফলনকারী API চালু করা হয়েছে যা
scheduler.postTask()
এবংscheduler.yield()
মতো উদ্বেগের সমাধান করে।
উপসংহার
কাজ পরিচালনা করা চ্যালেঞ্জিং, তবে এটি করা নিশ্চিত করে যে আপনার পৃষ্ঠাটি ব্যবহারকারীর মিথস্ক্রিয়ায় আরও দ্রুত প্রতিক্রিয়া জানায়। কার্যগুলি পরিচালনা ও অগ্রাধিকার দেওয়ার জন্য কোনও একক পরামর্শ নেই, বরং বিভিন্ন কৌশলগুলির একটি সংখ্যা নেই। পুনরাবৃত্তি করার জন্য, কার্যগুলি পরিচালনা করার সময় আপনি বিবেচনা করতে চান এমন প্রধান বিষয়:
- সমালোচনামূলক, ব্যবহারকারী-মুখোমুখি কাজের জন্য মূল থ্রেডে ফলন করুন।
- এরগনোমিকভাবে ফলন করতে এবং অগ্রাধিকারপ্রাপ্ত ধারাবাহিকতা পেতে
scheduler.yield()
(ক্রস ব্রাউজার ফ্যালব্যাক সহ) ব্যবহার করুন - অবশেষে, আপনার ফাংশনগুলিতে যথাসম্ভব সামান্য কাজ করুন।
scheduler.yield()
সম্পর্কে scheduler.postTask()
জানতে ।
এই সরঞ্জামগুলির এক বা একাধিক সহ, আপনার অ্যাপ্লিকেশনটিতে কাজটি কাঠামো তৈরি করতে সক্ষম হওয়া উচিত যাতে এটি ব্যবহারকারীর প্রয়োজনগুলিকে অগ্রাধিকার দেয়, যখন নিশ্চিত করে যে কম সমালোচনামূলক কাজটি এখনও সম্পন্ন হয়। এটি আরও ভাল ব্যবহারকারীর অভিজ্ঞতা তৈরি করতে চলেছে যা আরও প্রতিক্রিয়াশীল এবং ব্যবহারের জন্য আরও উপভোগযোগ্য।
ফিলিপ ওয়ালটনকে এই গাইডের প্রযুক্তিগত পরীক্ষা করার জন্য বিশেষ ধন্যবাদ।
আমিরালি মিরহাশেমিয়ান সৌজন্যে আনস্প্ল্যাশ থেকে উত্সাহিত থাম্বনেইল চিত্র।