لمحة عن مرحلة التجربة والتقييم في Scheduler.yield

كان إنشاء مواقع إلكترونية تستجيب بسرعة للبيانات التي يدخلها المستخدمون من بين الجوانب الأكثر تحديًا في أداء الويب، وهو ما يعمل فريق Chrome جاهدًا على مساعدته في تحقيقه. في هذا العام فقط، تم الإعلان عن أنّ مقياس مدى استجابة الصفحة لتفاعلات المستخدم (INP) سينتقل من الحالة التجريبية إلى الحالة المعلّقة. ومن المقرر أن يحلّ محلّ مهلة الاستجابة الأولى (FID) كأحد مؤشرات أداء الويب الأساسية في آذار (مارس) 2024.

في إطار الجهود المتواصلة لتقديم واجهات برمجة تطبيقات جديدة تساعد مطوّري الويب في جعل مواقعهم الإلكترونية سريعة قدر الإمكان، يجري فريق Chrome حاليًا تجربة أولى لscheduler.yield بدءًا من الإصدار 115 من Chrome. scheduler.yield هي إضافة جديدة مقترَحة لواجهة برمجة التطبيقات الخاصة بجدول التشغيل تتيح طريقة أسهل وأفضل لإعادة التحكّم إلى سلسلة المهام الرئيسية مقارنةً بالطُرق التي كان يُعتمَد عليها تقليديًا.

عند التسليم

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

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

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

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

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

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

مشكلة استراتيجيات تحقيق الأرباح الحالية

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

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

للاطّلاع على هذا الإجراء، جرِّب العرض التجريبي لتطبيق Glitch، أو جرِّبه في الإصدار المضمّن أدناه. يتألّف العرض الترويجي من بضعة أزرار يمكنك النقر عليها، ومربّع أسفل هذه الأزرار يسجّل وقت تنفيذ المهام. عند الانتقال إلى الصفحة، اتّبِع الخطوات التالية:

  1. انقر على الزر العلوي الذي يحمل العنوان تنفيذ المهام بشكل دوري، ما سيؤدي إلى جدولة المهام الحظرية لتشغيلها من حين لآخر. عند النقر على هذا الزر، سيتم ملء سجلّ المهام بعدة رسائل نصها تم تنفيذ مهمة الحظر باستخدام setInterval.
  2. بعد ذلك، انقر على الزرّ تشغيل حلقة، مع عرض setTimeout في كل تكرار.

ستلاحظ أنّ المربّع في أسفل العرض التوضيحي سيعرض رسالة مماثلة لما يلي:

Processing loop item 1
Processing loop item 2
Ran blocking task via setInterval
Processing loop item 3
Ran blocking task via setInterval
Processing loop item 4
Ran blocking task via setInterval
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval

يوضّح هذا الإخراج سلوك "نهاية قائمة انتظار المهام" الذي يحدث عند التوقّف باستخدام setTimeout. تعالج الحلقة التي يتم تشغيلها خمسة عناصر، وتُعرِض setTimeout بعد معالجة كل عنصر.

يوضّح ذلك مشكلة شائعة على الويب: من الشائع أن يسجِّل نص برمجي، خاصةً نص برمجي تابع لجهة خارجية، وظيفة موقّت تُنفِّذ العمل على فترات زمنية معيّنة. إنّ سلوك "نهاية قائمة انتظار المهام" الذي يأتي مع التوقّف باستخدام setTimeout يعني أنّه قد يتم وضع العمل من مصادر المهام الأخرى في قائمة الانتظار قبل العمل المتبقّي الذي يجب أن تنفّذه الحلقة بعد التوقّف.

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

إدخال scheduler.yield

كانت ميزة scheduler.yield متاحة كميزة تجريبية لمنصّة الويب منذ الإصدار 115 من Chrome. قد يخطر لك سؤال: "لماذا أحتاج إلى دالة خاصة لعرض النتائج عندما تؤدي دالة setTimeout ذلك؟"

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

scheduler.yield هي دالة تُسلّم التحكم إلى سلسلة المحادثات الرئيسية وتُعرِض Promise عند استدعائها. وهذا يعني أنّه يمكنك await في دالة async:

async function yieldy () {
  // Do some work...
  // ...

  // Yield!
  await scheduler.yield();

  // Do some more work...
  // ...
}

للاطّلاع على scheduler.yield قيد التنفيذ، اتّبِع الخطوات التالية:

  1. انتقِل إلى chrome://flags.
  2. فعِّل تجربة ميزات قاعدة الويب التجريبية. قد تحتاج إلى إعادة تشغيل Chrome بعد إجراء ذلك.
  3. انتقِل إلى الصفحة التجريبية أو استخدِم النسخة المضمّنة منها أسفل هذه القائمة.
  4. انقر على الزر العلوي الذي يحمل العنوان تنفيذ المهام بشكل دوري.
  5. أخيرًا، انقر على الزر تشغيل حلقة، مع عرض scheduler.yield في كل تكرار.

ستظهر النتيجة في المربّع في أسفل الصفحة على النحو التالي:

Processing loop item 1
Processing loop item 2
Processing loop item 3
Processing loop item 4
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval

على عكس التسجيل التجريبي الذي يُعطِل باستخدام setTimeout، يمكنك ملاحظة أنّ الحلقة، على الرغم من أنّها تُعطِل بعد كل تكرار، لا تُرسِل العمل المتبقّي إلى نهاية "قائمة الانتظار"، بل إلى مقدمتها. يمنحك ذلك أفضل ما في الحالتَين: يمكنك التوقّف عن عرض الإعلانات لتحسين استجابة الإدخال على موقعك الإلكتروني، ولكن يمكنك أيضًا التأكّد من عدم تأخّر العمل الذي أردت إكماله بعد التوقّف عن عرض الإعلانات.

ننصحك بتجربة ذلك.

إذا كانت ميزة scheduler.yield تبدو مثيرة للاهتمام وتريد تجربتها، يمكنك إجراء ذلك بطريقتَين بدءًا من الإصدار 115 من Chrome:

  1. إذا كنت تريد تجربة scheduler.yield على الجهاز، اكتب chrome://flags وأدخِله في شريط عناوين Chrome، ثم اختَر تفعيل من القائمة المنسدلة في قسم ميزات منصة الويب التجريبية. سيؤدي ذلك إلى إتاحة scheduler.yield (وأي ميزات تجريبية أخرى) في نسختك من Chrome فقط.
  2. إذا كنت تريد تفعيل scheduler.yield لمستخدمي Chromium الحقيقيين على مصدر يمكن للجميع الوصول إليه، عليك الاشتراك في مرحلة تقييم وتجربة scheduler.yield. يتيح لك ذلك تجربة الميزات المقترَحة بأمان لفترة زمنية معيّنة، كما يقدّم لفريق Chrome إحصاءات قيّمة حول كيفية استخدام هذه الميزات في المجال. لمزيد من المعلومات حول آلية عمل الفترات التجريبية للإصدارات الأصلية، يُرجى قراءة هذا الدليل.

تعتمد طريقة استخدام scheduler.yield، مع مواصلة إتاحة المتصفحات التي لا تطبّقه، على أهدافك. يمكنك استخدام المكتبة الرسمية لإضافة وظائف إلى الويب. يكون الpolyfill مفيدًا إذا كان ما يلي ينطبق على حالتك:

  1. أنت تستخدم حاليًا scheduler.postTask في تطبيقك لجدولة المهام.
  2. إذا كنت تريد تحديد أولويات المهام والأرباح
  3. تريد أن تتمكّن من إلغاء المهام أو إعادة تحديد أولوياتها من خلال فئة TaskController التي تقدّمها واجهة برمجة التطبيقات scheduler.postTask.

إذا لم يكن هذا الوصف يناسب حالتك، قد لا يكون ملف الpolyfill مناسبًا لك. في هذه الحالة، يمكنك طرح الإصدار الاحتياطي بطريقتَين. تستخدِم الطريقة الأولى الرمز scheduler.yield إذا كان متاحًا، ولكنّها تعود إلى الرمز setTimeout إذا لم يكن متاحًا:

// A function for shimming scheduler.yield and setTimeout:
function yieldToMain () {
  // Use scheduler.yield if it exists:
  if ('scheduler' in window && 'yield' in scheduler) {
    return scheduler.yield();
  }

  // Fall back to setTimeout:
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

// Example usage:
async function doWork () {
  // Do some work:
  // ...

  await yieldToMain();

  // Do some other work:
  // ...
}

يمكن أن تنجح هذه الطريقة، ولكن كما يمكنك تخمينه، لن تؤدي المتصفّحات التي لا تتوافق مع scheduler.yield إلى ظهور الإعلانات في "مقدّمة الصفّ". إذا كنت تفضّل عدم تحقيق أي عائد على الإطلاق، يمكنك تجربة نهج آخر يستخدم scheduler.yield إذا كان متاحًا، ولكن لن يحقّق أي عائد على الإطلاق في حال عدم توفّره:

// A function for shimming scheduler.yield with no fallback:
function yieldToMain () {
  // Use scheduler.yield if it exists:
  if ('scheduler' in window && 'yield' in scheduler) {
    return scheduler.yield();
  }

  // Fall back to nothing:
  return;
}

// Example usage:
async function doWork () {
  // Do some work:
  // ...

  await yieldToMain();

  // Do some other work:
  // ...
}

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

الصورة الرئيسية من Unsplash، لأحد أعمال Jonathan Allison.