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

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

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

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

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

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.

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

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

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';
}

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

إدخال العاملين في الوحدة

يتوفّر وضع جديد للعاملين على الويب الذين تتوفّر لديهم مزايا بيئة العمل والأداء لوحدات JavaScript وهو الشحن في Chrome 80، ويُطلق عليه اسم "عاملي الوحدات". تقبل دالة الإنشاء 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 عالمية الحدوث تشير إلى النطاق العمومي. وهو متوفر في جميع أنواع العاملين، بما في ذلك عاملي الخدمة، وكذلك في نموذج كائن المستند (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 بدءًا من إصدار Chrome 83. كما هو الحال مع العاملين المتخصّصين، يؤدي إنشاء عامل مشترك باستخدام الخيار {type:"module"} إلى تحميل النص البرمجي الخاص بالعامل كوحدة بدلاً من نص برمجي كلاسيكي:

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

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

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

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

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

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

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