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

أصبح نقل الأعباء الثقيلة إلى سلاسل المحادثات في الخلفية أسهل باستخدام وحدات 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 متاحة في معظم المتصفحات لأكثر من عشر سنوات. ويعني ذلك أنّ العاملين لديهم دعم ممتاز للمتصفّح ومحسّنة بشكل جيد، ولكن يعني ذلك أيضًا أنّهم يسبق لهم استخدام وحدات 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 عند استخدامها مع مشغّلي الخدمة. يجب أيضًا أن يتمكن عاملو الخدمة من تجاوز ذاكرة التخزين المؤقت للنصوص البرمجية في حالات معيّنة عند البحث عن التحديثات.

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