Web Worker چیست و چه کمکی به ما می‌کند؟

?What is Web Worker

25 آبان 1400
Web Worker چیست و چه کمکی به ما می کند؟

در سال های اولیه ساخت جاوا اسکریپت هیچکس نگران بحث سرعت و پردازش آن نبود چرا که جاوا اسکریپت در هر سایت معمولا فایل های اسکریپتی کوچکی بود که به بخش کوچکی از سایت زندگی می بخشید اما با گذر زمان این موضوع به طور کامل تغییر کرد تا جایی که در دنیای وب امروزی بسیاری از سایت ها به طور کامل با جاوا اسکریپت ساخته می شوند.

اینجاست که Web worker ها وارد صحنه می شوند. همانطور که می دانید کدهای جاوا اسکریپت در حالت عادی در main thread اجرا می شوند، یعنی همه چیز متوقف می شود تا اجرای آن ها تکمیل شود. حالا فرض کنید دکمه ای در سایت شما وجود دارد که عملیات سنگینی را انجام می دهد. اگر کدهای مربوط به این دکمه را به شکل عادی بنویسید همه چیز در سایت شما متوقف می شود و تا زمانی که این عملیات تمام نشده باشد کاربر باید منتظر بماند. اگر این عملیات بیش از حد سنگین باشد نیز خطای معروف Page Unresponsive را دریافت خواهید کرد:

خطای معروف Page Unresponsive
خطای معروف Page Unresponsive

در این حالت بهتر است این عملیات سنگین را در پس زمینه سایت (یعنی روی یک thread دیگر) اجرا کنیم تا کاربر بتواند با وب سایت ارتباط داشته باشد. web worker ها این کار را برای ما انجام می دهند و مراحل استفاده از آن ها نیز به شکل زیر است:

  • در ابتدا یک web worker جدید را می سازید.
  • در مرحله بعدی به web worker می گویید چه کاری انجام بدهد.
  • حالا web worker را اجرا می کنید.
  • زمانی که کار web worker تمام شود به شما اطلاع می دهد و از آن به بعد می توانید کارتان را بر اساس نتایج به دست آمده ادامه بدهید.

چه زمانی از web worker ها استفاده کنیم؟

استفاده از web worker ها فقط زمانی مفید است که عملیات مورد نظر شما بسیار سنگین باشد. عملیات های ساده هیچ نیازی به web worker ندارند، بلکه فقط کارتان را پیچیده تر می کنند. فرض کنید بخواهیم اعداد اول را از یک بازه عددی پیدا کنیم. من یک فایل HTML می سازم که محتویات زیر را داشته باشد:

<p>Do a prime number search from <input id="from" value="1"> to <input id="to" value="500000" size="200">.</p>

<button onclick="doSearch()">Start Searching</button>




<div id="primeContainer">

</div>




<div id="status"></div>

کمی کد CSS نیز به آن اضافه می کنیم:

body {

  font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;

  margin: 20px;

}




#primeContainer {

  border: solid 1px black;

  padding: 3px;

  height: 300px;

  max-width: 500px;

  overflow: scroll;

  overflow-x: hidden;

  font-size: x-small;

  margin-top: 20px;

  margin-bottom: 10px;

}




input {

  width: 80px;

}




button {

  padding: 3px;

}




p {

  margin-bottom: 3px;

}




#status {

  color: darkred;

}

و در نهایت کدهای جاوا اسکریپتی خود برای پیدا کردن اعداد اول را می نویسیم:

function doSearch() {

  // دریافت بازه جست و جو

  var fromNumber = document.getElementById("from").value;

  var toNumber = document.getElementById("to").value;




  var statusDisplay = document.getElementById("status");

  statusDisplay.innerHTML = "Starting new search...";   




  // اجرا کردن جست و جو

  var primes = findPrimes(fromNumber, toNumber);




  // دریافت نتایج و گردش در آن ها

  // و سپس قرار دادن آن ها در یک رشته طولانی

  var primeList = "";

  for (var i=0; i<primes.length; i++) {

    primeList += primes[i];

    if (i != primes.length-1) primeList += ", ";

  }




  // نمایش لیست اعداد اول در صفحه

  var primeContainer = document.getElementById("primeContainer");

  primeContainer.innerHTML = primeList;




  var statusDisplay = document.getElementById("status");

  if (primeList.length == 0) {

    statusDisplay.innerHTML = "Search didn't find any results.";

  }

  else {

    statusDisplay.innerHTML = "The results are here!";

  }

}







function findPrimes(fromNumber, toNumber) {




  // ساخت یک آرایه که حاوی تمام اعداد صحیح بین دو بازه مشخص شده باشد

  var list = [];

  for (var i=fromNumber; i<=toNumber; i++) {

    if (i>1) list.push(i);

  }




  // جست و جو به دنبال اعداد اول

  var maxDiv = Math.round(Math.sqrt(toNumber));

  var primes = [];




  for (var i=0; i<list.length; i++) {

    var failed = false;

    for (var j=2; j<=maxDiv; j++) {

      if ((list[i] != j) && (list[i] % j == 0)) {

        failed = true;

      } else if ((j==maxDiv) && (failed == false)) {

        primes.push(list[i]);

      }

    }

  }




  return primes;

}

شما می توانید این کد را در این لینک مشاهده کنید.

پیشنهاد می دهم ابتدا بازه را بین اعداد ۱ و ۱۰۰۰۰۰ مشخص کنید تا اعداد اول در آن پیدا شوند. این عملیات بسیار سریع اتفاق می افتد و مشکلی نخواهید داشت. حالا یک بار دیگر این کار را با بازه ای بین ۱ تا ۱۰۰۰۰۰۰ انجام بدهید. با انجام این کار سیستم شما تا چند ثانیه قفل می کند و قابلیت کلیک کردن یا اسکرول کردن را از دست می دهید.

اگر به کد بالا دقت کنید متوجه خواهید شد که کلیک روی دکمه باعث اجرای متدی به نام doSearch می شود که تنها کارش نمایش اعداد اول پیدا شده در مرورگر است بنابراین ما اصلا با متد doSearch کاری نداریم چرا که بار پردازشی روی دوش آن نیست. متد دیگری به نام findPrimes وجود دارد که تمام بار پردازشی را به دوش می کشد و ما می خواهیم web worker خاصی را برای این متد ایجاد کنیم تا در هنگام محاسبه اعداد، صفحه برای کاربر قفل نشود. چرا؟ به دلیل اینکه ما نمی دانیم کاربر چه بازه ای را وارد خواهد کرد بنابراین web worker ها برای چنین برنامه ای ضروری هستند.

نحوه کارکرد web worker ها

کلاسی به نام Worker در جاوا اسکریپت وجود دارد که قلب web worker ها است. اگر بخواهیم یک web worker را بسازیم باید یک نمونه از این کلاس را ایجاد کنیم و کدهای جاوا اسکریپت مورد نظرمان را به آن پاس بدهیم. باید توجه داشته باشید که کدهای پاس داده شده به Worker باید در یک فایل جداگانه ذخیره شوند.

یک مثال از نحوه ساخت web worker ها را در کد زیر می بینید:

var worker = new Worker("PrimeWorker.js");

چرا باید کدهای web worker ها را در یک فایل جداگانه قرار بدهیم؟ به دلیل اینکه اگر دو thread به صورت همزمان سعی در ویرایش متغیر های سراسری داشته باشند برنامه با مشکلات جدی مواجه می شود. این مسئله بدین معنی است که کدهای درون فایل PrimeWorker.js در مثال بالا به هیچ عنوان اجازه نوشتن اعداد یا هر مقدار دیگری را در یکی از div های صفحه ندارند. به جای این کار web worker داده های محاسبه شده را به فایل جاوا اسکریپت اصلی می فرستد و این فایل اصلی است که داده ها را در صفحه نمایش می دهد.

سوالی که پیش می آید اینجاست که web worker و صفحه اصلی چطور با هم ارتباط دارند؟ متدی به نام postMessage در worker وجود دارد که داده ها را به صفحه اصلی ارسال می کند:

worker.postMessage(myData);

از طرفی صفحه اصلی رویدادی به نام onMessage را دارد که توسط آن داده های ارسال شده را دریافت می کند. در ضمن نظر داشته باشید که متد postMessage فقط یک آرگومان را قبول می کند بنابراین اگر داده های زیادی داشته باشید باید آن ها را در یک شیء قرار بدهید:

worker.postMessage(

  { from: 1,

    to: 20000 }

);

ساخت یک web worker

با توضیحاتی که در بخش قبل دادم حالا می توانیم کدهایمان را تصحیح کنیم. این بار کدها را طوری می نویسیم که متد doSearch یک worker ساخته و از آن برای تولید اعداد اول استفاده کند:

var worker;




function doSearch() {

  // دکمه را غیر فعال کرده ایم تا کاربر

  // در یک زمان چندین بار جست و جو را اجرا نکند

  searchButton.disabled = true;




  // یک ورکر جدید می سازیم

  worker = new Worker("PrimeWorker.js");




  // رویداد مناسب را تنظیم می کنیم تا بتوانیم

  // پیام های ارسال شده از سمت ورکر را دریافت کنیم

  worker.onmessage = receivedWorkerMessage;




  // بازه مورد نظر کاربر را گرفته و به ورکر ارسال می کنیم

  var fromNumber = document.getElementById("from").value;

  var toNumber = document.getElementById("to").value;




  worker.postMessage(

   { from: fromNumber,

     to: toNumber }

  );




  // به کاربر اعلام می کنیم که در حال انجام عملیات هستیم

  statusDisplay.innerHTML = "A web worker is on the job ("+

   fromNumber + " to " + toNumber + ") ...";

}

با انجام این کار کدهای موجود در فایل PrimeWorker.js شروع به کار می کنند. پس از اینکه کارش تمام شد یک message را به صفحه اصلی برمی گرداند:

onmessage = function(event) {

  // حضور دارند event.data اطلاعات ارسال شده از صفحه اصلی در

  var fromNumber = event.data.from;

  var toNumber = event.data.to;




  // عملیات جست و جوی اعداد اول را انجام می دهیم

  var primes = findPrimes(fromNumber, toNumber);




  // حالا که عملیات تمام شده است نتیجه را به صفحه اصلی ارسال می کنیم

  postMessage(primes);

};




function findPrimes(fromNumber, toNumber) {

  // کدهایی که برای پیدا کردن اعداد اول نوشته بودیم در این قسمت خواهند بود. من برای جلوگیری از تکرار آن ها را نیاورده ام.

}

زمانی که متد postMessage اجرا می شود، رویداد onMessage نیز اجرا می شود که به نوبه خودش باعث اجرا شدن کد زیر در صفحه اصلی می شود:

function receivedWorkerMessage(event) {

  // دریافت لیست اعداد اول

  var primes = event.data;




  // کپی کردن لیست اعداد در صفحه اصلی

  ...




  // دکمه را دوباره فعال کنید تا کاربر بتواند جست و جوهای دیگری انجام بدهد

  searchButton.disabled = false;

}

اگر می خواهید کل این کدها را در کنار هم مشاهده کنید به این لینک بروید و عدد بزرگی را به عنوان بازه خود انتخاب کنید.

مدیریت خطا در workerها

در صورتی که خطایی در worker به وجود بیاید چطور می توانیم آن ها را مدیریت کنیم؟ روی هر worker یک خصوصیت به نام onerror وجود دارد که به شما اجازه می دهد خطا ها را مدیریت کنید:

worker.onerror = workerError;

با انجام این کار اگر خطایی رخ بدهد، آن خطا مستقیما به صفحه اصلی منتقل می شود و طبیعتا ما می توانیم با کد ساده ای مانند کد زیر آن را مدیریت کنیم:

function workerError(error) {

  statusDisplay.textContent = error.message;

}

همانطور که می بینید روی شیء error که به صورت خودکار پاس داده می شود، خصوصیتی به نام message وجود دارد که متن خطا است. همچنین خصوصیات دیگری مانند lineno (شماره خطی که باعث خطا شد) و filename (نام فایلی که باعث خطا شد) نیز وجود دارد.

متوقف کردن یک worker

در صورتی که می خواهید یک worker متوقف شود، می توانید متد ()close را درون خودش صدا بزنید اما اگر می خواهید worker را از صفحه اصلی متوقف کنید باید متد ()terminate را روی آن صدا بزنید:

function cancelSearch() {

  worker.terminate();

  statusDisplay.textContent = "";

  searchButton.disabled = false;

}

به عنوان نکته آخر باید اشاره کنم که شما می توانید درون یک web worker از یک web worker دیگر استفاده کنید (قابلیت تو در تو بودن).


منبع: وب سایت medium

نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.