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

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

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

على تحقيق

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

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

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

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

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

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

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

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

صورة رئيسية من Unسباش، بقلم جوناثان أليسون.