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

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

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

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

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

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() في مهام Worker على الويب لتحميل رمز إضافي، ولكنها توقِف مؤقتًا عن تنفيذ المهمة من أجل جلب كل نص برمجي وتقييمه. وتعمل أيضًا على تنفيذ النصوص البرمجية في النطاق العام مثل علامة <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() ضمن module workers. يعني تبديل وحدات العمل لاستخدام وحدات 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>

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

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

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