لمحة عن مرحلة التجربة والتقييم في 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.