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

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

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

عند تحقيق الأرباح

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

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

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

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

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

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

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

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

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

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

  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. فعِّل تجربة ميزات Web Platform التجريبية. قد تحتاج إلى إعادة تشغيل 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 الرسمي. يكون رمز 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" مفيدة بالنسبة إليك، يُرجى المشاركة في أبحاثنا للمساعدة في تحسينها وتقديم ملاحظاتك حول كيفية تحسينها.

صورة رئيسية من UnLaunch، للفنان جوناثان أليسون