در 20 سال گذشته، وب از نمایش اسناد HTML با چند استایل و تصویر و چند خط کد جاوا اسکریپت برای پویا سازی صفحه، گذر کرده است و هم اکنون به اکوسیستمی پیچیده و بسیار پویا تبدیل شده است. با این حال، یک چیز تقریباً بدون تغییر باقی مانده است: ما فقط یک Thread به ازای هر تب مرورگر برای انجام کار rendering سایتها و اجرای جاوا اسکریپت خود داریم. در نتیجه، Main Thread به شدت پرکار شده است و با افزایش پیچیدگی Web Appها، Main Thread به یک گلوگاه قابل توجهی در برابر Performance و کارایی تبدیل شده است. یک راه برای حل این مشکل استفاده از Web worker در جاوا اسکریپت است.
مدت زمانی که برای اجرای کد روی Main Thread برای یک کاربر خاص لازم است، تقریباً کاملاً غیرقابل پیشبینی است چراکه قابلیتهای دستگاه تأثیر عمیقی بر کارایی دارند. کاربران وب ممکن است با گوشیهای ساده که محدودیت شدید کارایی دارند و یا با دستگاههای پرچمدار قدرتمند با نرخ Refresh Rate بالا، به وب دسترسی پیدا کنند.
اگر میخواهیم برنامههای وب پیچیده، معیار های Core Web Vitals را رعایت کنند - که مبتنی بر دادههای تجربی در مورد ادراک و روانشناسی انسان است – نیاز به اجرای کد خود خارج از Thread اصلی را داریم. اینجاست که Web worker در جاوا اسکریپت ها می توانند کمک کننده باشند.
چرا از Web worker در جاوا اسکریپت استفاده کنیم؟
جاوا اسکریپت به طور پیشفرض یک زبان تک نخی یا Single Thread است که وظایف را روی Main Thread اجرا میکند. با این حال، Web Worker نوعی دریچه فرار از Main Thread با هدف ایجاد نخ های جداگانه برای انجام کار خارج از Main Thread ارائه می دهند. در حالی که قلمرو Web worker در جاوا اسکریپت محدود است و دسترسی مستقیم به DOM را ندارند، با این حال، می توانند برای انجام کار های پردازشی بسیار سودمند باشند.
در مورد Core Web Vitals، اجرای کار خارج از Main Thread می تواند مفید باشد. به طور خاص، بارگذاری کار از Main Thread به Web Worker میتواند بار پردازشی Main Thread را کاهش دهد، در نتیجه معیار پاسخدهی یک صفحه (به طور دقیق تر Interaction to Next Paint) بهبود پیدا می کند. وقتی Main Thread کار کمتری برای پردازش داشته باشد، می تواند سریعتر به تعاملات کاربر پاسخ دهد و معیار Responsiveness صفحه، افزایش پیدا می کند. منظور از Responsiveness، سرعت Web App در ارائه بازخورد به کاربر در جواب یک عمل از کاربر است و نباید با Responsive کردن ظاهری سایت، اشتباه گرفته شود.
علاوه بر این، با سپردن کار های مختلف به Web Worker و کاهش بار Main Thread، به ویژه در تسک های راه اندازی اولیه (Initialization – تسک هایی که در هنگام لود اولیه سایت باید انجام شوند) معیار LCP – Largest Contentful Paint که وابسته به سرعت لود شدن بزرگترین المان از Viewport اولیه سایت است، بهبود پیدا می کند، چراکه Render کردن متن یا تصاویر، که عناصر متداول و رایج LCP هستند، به Thread اصلی نیاز داریم و بار روی دوش این Thread است. با کاهش کلی Main Thread، میتوانید اطمینان حاصل کنید که عنصر LCP شما خیلی زود تر توسط Thread اصلی Render می شود.
یجاد Thread با Web Worker ها
پلتفرمهای دیگر معمولاً از موازی سازی یا پاراللیسم پشتیبانی میکنند و به شما اجازه میدهند به یک Thread تابعی بدهید که به موازات بقیه برنامههای شما (به شکل کاملا همزمان روی Core های مختلف دستگاه) اجرا میشود. شما می توانید از هر دو Thread به متغیرهای یکسانی دسترسی داشته باشید و دسترسی به این منابع مشترک را می توان با mutexes و semaphores همگام کرد تا از شرایط Racing جلوگیری شود – این همان مسائلی است که مربوط به هگام سازی و یا Synchronization در Process و Thread می شود.
این مسائل در زبان های سطح پایین مثل C، C++ و یا Rust وجود دارد که باید به آن توجه بالایی داشت. در جاوا اسکریپت، ما میتوانیم عملکردهای تقریباً مشابهی را از Web Worker ها انتظار داشته باشیم، که از سال 2007 وجود داشته و در سال 2012 در تمام مرورگرهای اصلی پشتیبانی شد. کارگران وب بهطور موازی با رشته اصلی کار میکنند، اما برخلاف رشتههای سیستمعامل، نمیتوانند متغیرها را به اشتراک بگذارند.
در جاوا اسکریپت، ما میتوانیم عملکردهای تقریباً مشابهی را از Web worker ها انتظار داشته باشیم، که از سال 2007 وجود داشته و از سال 2012 در تمام مرورگرهای اصلی پشتیبانی میشوند. Web worker ها بهطور موازی با Main Thread کار میکنند، اما برخلاف Thread های سیستمعامل که در زبان های سطح پایین با آنها کار می کنیم، نمیتوانند متغیرها را به اشتراک بگذارند.
برای ایجاد یک Web Worker، یک فایل را به سازنده Worker ورودی دهید، که شروع به اجرای آن فایل در یک رشته جداگانه می کند.
در فایل main.js
const worker = new Worker("./worker.js");
برای ارسال پیام به این Worker ساخته شده، از تابع postMessage استفاده کنید. مقدار پیام را به عنوان یک پارامتر در postMessage ارسال کنید و سپس یک Event Listener را به Worker اضافه کنید.
const worker = new Worker('./worker.js');
worker.postMessage([40, 2]);
در فایل worker.js
addEventListener('message', event => {
const [a, b] = event.data;
// Do stuff with the message
// ...
});
برای ارسال پیام به Main Thread، از همان postMessage API در Web Worker استفاده کنید و Event Listener را در Main Thread تنظیم کنید.
در فایل main.js
const worker = new Worker('./worker.js');
worker.postMessage([40, 2]);
worker.addEventListener('message', event => {
console.log(event.data);
});
در فایل worker.js
addEventListener('message', event => {
const [a, b] = event.data;
// Do stuff with the message
postMessage(a + b);
});
مسلماً این رویکرد تا حدودی محدود است. از لحاظ تاریخی، Web Worker ها عمدتاً برای جابجایی یک تکه کار سنگین از Main Thread استفاده میشدهاند. تلاش برای انجام چندین عملیات با یک Web Worker به سرعت دشوار میشود: شما نه تنها باید پارامترها، بلکه چیستی تابع و عملایت روی آن پارامتر را در ساختار پیام قرار دهید و در ادامه باید پاسخها به درخواست هایی که ارسال می کنید تطابق داشته باشد. اما اگر بتوانیم برخی از مشکلات برقراری ارتباط بین Main Thread و Web Worker را حذف کنیم، این مدل می تواند برای بسیاری از موارد استفاده مناسب باشد. و خوشبختانه، کتابخانه ای وجود دارد که این کار را انجام می دهد!
معرفی کتابخانه Comlink برای استفاده آسان از Web worker در جاوا اسکریپت
Comlink کتابخانه ای است که هدف آن این است که به شما اجازه دهد بدون فکر کردن به جزئیات postMessage از Web Worker ها استفاده کنید. Comlink به شما امکان می دهد تا متغیرها را بین Web Worker و Main Thread تقریباً مانند سایر زبان های برنامه نویسی که از Thread های سیستم عاملی پشتیبانی می کنند به اشتراک بگذارید.
شما Comlink را با وارد کردن آن در Web Worker و تعریف مجموعهای از توابع برای استفاده در Main Thread راهاندازی میکنید. سپس در Thread اصلی از توابع Expose شده توسط Web Worker، استفاده می کنید.
در فایل Worker.js
import {expose} from 'comlink';
const api = {
someMethod() {
// ...
}
}
expose(api);
در فایل Main.js
import {wrap} from 'comlink';
const worker = new Worker('./worker.js');
const api = wrap(worker);
شی api در main.js، یک مجموعه RPC یا Remote Procedure Call به شی api در فایل worker.js است که کار فراخوانی توابع را بسیار ساده تر می کند. خروجی توابع موجود در شی api در فایل main.js، همگی Promise برمیگردانند چراکه ارتباط بین دو Thread به شکل ناهمزمان یا Async در اجرا است.
چه کدی رو باید به Web worker در جاوا اسکریپت سپرد؟
Web Worker ها به APIs DOM و بسیاری از Web APIs مانند WebUSB، WebRTC یا Web Audio دسترسی ندارند، بنابراین نمیتوانید قطعاتی از برنامه خود را که به چنین دسترسیهایی متکی هستند در یک Worker قرار دهید. با این حال، هر کد کوچکی که به یک Worker منتقل میشود، فضای بیشتری را در Main Thread برای چیزهایی که باید وجود داشته باشد، باز میکند - مانند بهروزرسانی رابط کاربری یا همان Re-Render صفحه.
برای اطمینان از اینکه برنامههای ما تا حد امکان قابل اعتماد و در دسترس هستند، بهویژه در بازاری که بهطور فزایندهای جهانی شده است، باید از دستگاههای محدود پشتیبانی کنیم—اینگونه است که اکثر کاربران در سطح جهانی به وب دسترسی دارند. OMT یا Off the Main Thread روشی امیدوارکننده برای افزایش عملکرد در چنین دستگاه هایی ارائه می دهد.
به طور خلاصه OMT مزایای زیر را دارد:
- هزینه های اجرای جاوا اسکریپت را به یک Thread جداگانه منتقل می کند.
- هزینه های تجزیه را کاهش می دهد، به این معنی که UI ممکن است سریعتر راه اندازی شود. این ممکن است First Contentful Paint یا حتی Time to Interactive را کاهش دهد که به نوبه خود می تواند امتیاز Lighthouse شما را افزایش دهد.
کار با Web Worker در جاوا اسکریپت شاید کمی ترسناک باشد اما ابزارهایی مانند Comlink کار با Web Worker ها را بسیار ساده می کنند آنها را به انتخابی مناسب برای طیف وسیعی از Web App ها تبدیل می کنند.
حال که با Web Worker در جاوا اسکریپت آشنا شدید می توانید از دیگر مقالات سایت Evolearn | ایوولرن دیدن کنید.