توفير سلاسل محادثات على الويب باستخدام عمال الوحدات

أصبح من الأسهل الآن نقل المهام الصعبة إلى سلاسل المهام في الخلفية باستخدام وحدات JavaScript في مهام الويب.

JavaScript هو لغة برمجة أحادية السلسلة، ما يعني أنّه يمكنها تنفيذ عملية واحدة فقط في كل مرة. هذه الطريقة سهلة الاستخدام وتعمل بشكل جيد في الكثير من الحالات على الويب، ولكن يمكن أن تصبح مشكلة عندما نحتاج إلى تنفيذ مهام صعبة مثل معالجة البيانات أو تحليلها أو تحليلها أو تحليلها. مع تزايد عدد التطبيقات المعقدة على الويب، تزداد الحاجة إلى المعالجة المتعدّدة المهام.

على منصة الويب، تكون واجهة برمجة التطبيقات Web Workers API هي العنصر الأساسي لإنشاء مؤشرات الترابط والتوازي. يمثّل العاملون طريقة تجريد بسيطة في أعلى سلاسل محادثات نظام التشغيل تعرض واجهة برمجة تطبيقات لتمرير الرسائل من أجل الاتصال بين سلاسل المحادثات. قد يكون ذلك مفيدًا للغاية عند إجراء عمليات حسابية مكلفة أو العمل على مجموعات بيانات كبيرة، لأنّ سلسلة التعليمات الرئيسية تعمل بسلاسة أثناء تنفيذ العمليات المكلّفة على سلسلة أو أكثر من سلاسل البيانات في الخلفية.

في ما يلي مثال نموذجي على استخدام العامل، حيث يستمع النص البرمجي الخاص بالعامل إلى الرسائل من سلسلة المحادثات الرئيسية ويردّ عليه من خلال إرسال رسائل خاصة به:

page.js:

const worker = new Worker('worker.js');
worker.addEventListener('message', e => {
  console.log(e.data);
});
worker.postMessage('hello');

worker.js:

addEventListener('message', e => {
  if (e.data === 'hello') {
    postMessage('world');
  }
});

كانت Web Worker API متاحة في معظم المتصفّحات لأكثر من عشر سنوات. على الرغم من أنّ ذلك يعني أنّ تطبيقات العمال متوافقة مع المتصفّحات بشكلٍ رائع ومُحسَّنة بشكلٍ جيد، إلا أنّه يعني أيضًا أنّها سبقت وحدات JavaScript بوقت طويل. بما أنّه لم يكن هناك نظام وحدات عند تصميم "الموظفين"، ظلّت واجهة برمجة التطبيقات لتحميل الرمز البرمجي في "الموظف" وإنشاء النصوص البرمجية مشابهة لطرق تحميل النصوص البرمجية المتزامنة الشائعة في عام 2009.

السجلّ: العمال الكلاسيكيون

يأخذ مُنشئ Worker عنوان URL لسكريبت كلاسيكي ، والذي يرتبط بعنوان URL للمستند. ويعرض على الفور إشارة إلى مثيل Worker الجديد، الذي يعرِض واجهة مراسلة بالإضافة إلى طريقة terminate() التي تتوقف على الفور عن استخدام Worker و تُدمِره.

const worker = new Worker('worker.js');

تتوفّر دالة importScripts() ضمن عمال الويب لتحميل رمز إضافي، ولكنها توقِف مؤقتًا عن تنفيذ العامل من أجل جلب كل نص برمجي وتقييمه. وتعمل أيضًا على تنفيذ النصوص البرمجية في النطاق العام مثل علامة <script> الكلاسيكية، ما يعني أنّه يمكن أن يتم استبدال المتغيرات في نص برمجي بالمتغيرات في نص آخر.

worker.js:

importScripts('greet.js');
// ^ could block for seconds
addEventListener('message', e => {
  postMessage(sayHello());
});

greet.js:

// global to the whole worker
function sayHello() {
  return 'world';
}

لهذا السبب، كان لعمال الويب تأثير كبير في بنية التطبيقات. اضطر المطوّرون إلى إنشاء أدوات وحلول بديلة ذكية لتمكينهم من استخدام Web Workers بدون التخلي عن ممارسات التطوير الحديثة. على سبيل المثال، تُضمِّن حِزم مثل webpack عملية تنفيذ صغيرة لتحميل الوحدات في الرمز الذي تم إنشاؤه ويستخدم importScripts() لتحميل الرمز، ولكنّها تلف الوحدات في دوال لتجنُّب تداخل المتغيّرات ومحاكاة عمليات استيراد الملحقات وتصديرها.

إدخال عدد عمال الوحدة

يتوفّر في الإصدار 80 من Chrome وضع جديد لعمال الويب يقدّم مزايا وحدات JavaScript من حيث سهولة الاستخدام والأداء، ويُعرف باسم "عمال الوحدات". يقبل الآن باني Worker خيار {type:"module"} جديدًا، ما يؤدي إلى تغيير تحميل النص البرمجي و تنفيذه لمطابقة <script type="module">.

const worker = new Worker('worker.js', {
  type: 'module'
});

بما أنّ العاملين في الوحدات هم وحدات JavaScript عادية، يمكنهم استخدام عبارات الاستيراد والتصدير. كما هو الحال مع كل وحدات JavaScript، لا يتم تنفيذ التبعيات إلا مرة واحدة في سياق معيّن (السلسلة الرئيسية، العامل، وما إلى ذلك)، وتشير جميع عمليات الاستيراد المستقبلية إلى مثيل الوحدة الذي سبق تنفيذه. تعمل المتصفّحات أيضًا على تحسين عملية تحميل وحدات JavaScript وتنفيذها. يمكن تحميل تبعيات الوحدة قبل تنفيذها، ما يسمح بتحميل شجرات الوحدات بأكملها بالتوازي. وعند تحميل الوحدة النمطية، يتم أيضًا تخزين الرمز المحلّل مؤقتًا، ما يعني أنّ الوحدات المستخدمة في سلسلة التعليمات الرئيسية وفي العامل الذي يتطلب عملاً يجب تحليلها مرّة واحدة فقط.

يتيح أيضًا الانتقال إلى وحدات JavaScript استخدام الاستيراد الديناميكي لرمز التحميل الكسول بدون حظر تنفيذ العامل. إنّ الاستيراد الديناميكي أكثر وضوحًا بكثير من استخدام importScripts() لتحميل التبعيات، لأنّه يتم عرض عمليات تصدير الوحدة المستورَدة بدلاً من الاعتماد على المتغيّرات الشاملة.

worker.js:

import { sayHello } from './greet.js';
addEventListener('message', e => {
  postMessage(sayHello());
});

greet.js:

import greetings from './data.js';
export function sayHello() {
  return greetings.hello;
}

لضمان تحقيق أداء رائع، لا تتوفّر طريقة importScripts() القديمة ضمن مشغِّلي الوحدات. يعني تبديل العاملين لاستخدام وحدات JavaScript أنه يتم تحميل كل الرموز في الوضع المتشدد. هناك تغيير ملحوظ آخر وهو أنّ قيمة this في النطاق الأعلى لرموز JavaScript البرمجية هي undefined، في حين أنّ القيمة في "العمال الكلاسيكيين" هي النطاق العام للعامل. لحسن الحظ، كان هناك في السابق متغير self عام يقدّم إشارة إلى النطاق العام. وهو متاح في جميع أنواع مهام Worker، بما في ذلك مشغّلو الخدمات، بالإضافة إلى DOM.

تحميل بيانات العاملين تلقائيًا باستخدام "modulepreload"

من بين التحسينات الكبيرة في الأداء التي يوفّرها "عاملو الوحدات" هي إمكانية التحميل المُسبَق للعاملين وتبعاتهم. باستخدام وحدات العمال، يتم تحميل النصوص البرمجية وتنفيذها كوحدات JavaScript عادية، ما يعني أنّه يمكن تحميلها مسبقًا وحتى تحليلها مسبقًا باستخدام modulepreload:

<!-- preloads worker.js and its dependencies: -->
<link rel="modulepreload" href="worker.js">

<script>
  addEventListener('load', () => {
    // our worker code is likely already parsed and ready to execute!
    const worker = new Worker('worker.js', { type: 'module' });
  });
</script>

يمكن أيضًا استخدام الوحدات المحمَّلة مسبقًا من خلال سلسلة المهام الرئيسية وعمال الوحدات. يكون ذلك مفيدًا ل الوحدات التي يتم استيرادها في كلا السياقَين، أو في الحالات التي لا يمكن فيها معرفة ما إذا كان سيتم استخدام وحدة في سلسلة المهام الرئيسية أو في سلسلة مهام عامل.

في السابق، كانت الخيارات المتاحة للتحميل المُسبق للنصوص البرمجية الخاصة بالعاملين على الويب محدودة ولم تكن موثوقة بالضرورة. كان لدى العاملين في الوضع الكلاسيكي نوع مورد "عامل" خاص بالتحميل المُسبق، ولكن لم يتم تنفيذ أي متصفِّحات <link rel="preload" as="worker">. نتيجةً لذلك، كانت <link rel="prefetch"> هي التقنية الأساسية المتاحة لتحميل مهام معالجة الويب مسبقًا، والتي كانت تعتمد بالكامل على ذاكرة التخزين المؤقت لبروتوكول HTTP. عند استخدام هذه الرؤوس مع رؤوس التخزين المؤقت الصحيحة، أصبح من الممكن تجنُّب انتظار تنزيل النص البرمجي للمشغِّل عند إنشاء مثيل له. ومع ذلك، على عكس modulepreload، لم تكن هذه التقنية تتيح تحميل الملحقات مسبقًا أو التحليل المُسبَق.

ماذا عن العمال المشترَكين؟

تم تعديل العمال المشترَكون لتوفير دعم وحدات JavaScript اعتبارًا من الإصدار 83 من Chrome. كما هو الحال مع العاملين المتخصّصين، يؤدي إنشاء عامل مشترك باستخدام الخيار {type:"module"} إلى تحميل النص البرمجي الخاص بالعامل كوحدة بدلاً من نص برمجي كلاسيكي:

const worker = new SharedWorker('/worker.js', {
  type: 'module'
});

قبل إتاحة وحدات JavaScript، كان مُنشئ SharedWorker() يتوقّع فقط SharedWorker() ووسيطة اختيارية.name سيستمرّ هذا الإجراء في العمل مع استخدام وظائف العمال المشترَكة الكلاسيكية، ومع ذلك، فإنّ إنشاء وظائف العمال المشترَكة للوحدات يتطلّب استخدام الوسيطة options الجديدة. الخيارات المتوفّرة هي نفسها الخيارات المتاحة للعامل المخصّص، بما في ذلك خيار name الذي يحلّ محلّ الوسيطة name السابقة.

ماذا عن مشغّل الخدمات؟

سبق أن تم تعديل مواصفات مشغّل الخدمة للسماح بقبول ملف برمجي لخدمة {type:"module"} بتنسيق JavaScript كنقطة دخول، وذلك باستخدام خيار {type:"module"} نفسه المخصّص لمشغّلات الملفات البرمجية، ولكن لم يتم تنفيذ هذا التغيير في المتصفحات بعد. بعد حدوث ذلك، سيكون من الممكن إنشاء مثيل لخدمة عامل باستخدام وحدة JavaScript باستخدام الرمز التالي:

navigator.serviceWorker.register('/sw.js', {
  type: 'module'
});

بعد أن تم تعديل المواصفة، بدأت المتصفّحات في تنفيذ السلوك الجديد. يستغرق ذلك بعض الوقت لأنّ هناك بعض التعقيدات الإضافية المرتبطة بإدخال وحدات JavaScript إلى الخدمة العاملة. يجب أن يُجري تسجيل موظّف الخدمة مقارنة بين النصوص البرمجية المستورَدة وإصداراتها السابقة المخزّنة مؤقتًا عند تحديد ما إذا كان سيتم إجراء تحديث، ويجب تنفيذ ذلك مع وحدات JavaScript عند استخدامها مع موظّفي الخدمة. ويجب أن يكون عاملو الخدمة قادرين أيضًا على تجاوز ذاكرة التخزين المؤقت للنصوص البرمجية في بعض الحالات عند البحث عن تحديثات.

موارد إضافية وقراءة إضافية