كان إنشاء مواقع إلكترونية تستجيب بسرعة للبيانات التي يدخلها المستخدم أحد أصعب جوانب أداء الويب، وقد بذل فريق Chrome جهدًا كبيرًا لمساعدة مطوّري الويب في تحقيق ذلك. في هذا العام فقط، تم الإعلان عن نقل مقياس "مدى استجابة الصفحة لتفاعلات المستخدم" (INP) من حالة تجريبي إلى حالة في انتظار المراجعة. ومن المقرّر الآن أن يحلّ محلّ مقياس "مهلة الاستجابة الأولى" (FID) كأحد "مؤشرات أداء الويب الأساسية" في مارس 2024.
في إطار الجهود المستمرة لتوفير واجهات برمجة تطبيقات جديدة تساعد مطوّري الويب في تحسين سرعة استجابة مواقعهم الإلكترونية، يُجري فريق Chrome حاليًا مرحلة تجربة وتقييم لواجهة برمجة التطبيقات scheduler.yield بدءًا من الإصدار 115 من Chrome. scheduler.yield هي إضافة جديدة مقترَحة إلى واجهة برمجة التطبيقات Scheduler، وتتيح طريقة أسهل وأفضل لإعادة التحكّم إلى سلسلة المحادثات الرئيسية مقارنةً بـ الطرق التي تم الاعتماد عليها تقليديًا.
لمحة عن إعادة التحكّم
تستخدم JavaScript نموذج "التشغيل حتى الإكمال" للتعامل مع المهام. يعني ذلك أنّه عند تشغيل مهمة على سلسلة المحادثات الرئيسية، يتم تشغيل هذه المهمة طالما كان ذلك ضروريًا لإكمالها. عند اكتمال المهمة، تتم إعادة التحكّم إلى سلسلة المحادثات الرئيسية، ما يسمح لسلسلة المحادثات الرئيسية بمعالجة المهمة التالية في قائمة الانتظار.
باستثناء الحالات القصوى التي لا تنتهي فيها المهمة أبدًا، مثل حلقة لا نهائية على سبيل المثال، فإنّ إعادة التحكّم هي جانب لا مفر منه في منطق جدولة المهام في JavaScript. سيحدث ذلك بالتأكيد، ولكن الأهم هو متى يحدث، وكلما حدث مبكرًا كان ذلك أفضل. عندما تستغرق المهام وقتًا طويلاً لتشغيلها، أي أكثر من 50 ملي ثانية على وجه التحديد، تُعتبر مهامًا طويلة.
تتسبب المهام الطويلة في ضعف استجابة الصفحة، لأنّها تؤخّر قدرة المتصفّح على الاستجابة لبيانات أدخلها المستخدم. كلما زاد تكرار المهام الطويلة وزادت مدة تشغيلها، زاد احتمال أن يحصل المستخدمون على انطباع بأنّ الصفحة بطيئة، أو حتى يشعرون بأنّها معطّلة تمامًا.
مع ذلك، لا يعني مجرد أنّ الرمز البرمجي يبدأ مهمة في المتصفّح أنّه عليك الانتظار حتى تنتهي هذه المهمة قبل إعادة التحكّم إلى سلسلة المحادثات الرئيسية. يمكنك تحسين استجابة الصفحة للبيانات التي يدخلها المستخدم من خلال إعادة التحكّم بشكلٍ صريح في إحدى المهام، ما يؤدي إلى تقسيم المهمة ليتم إكمالها في الفرصة التالية المتاحة. يسمح ذلك للمهام الأخرى بالحصول على وقت على سلسلة المحادثات الرئيسية في وقت أقرب مما لو كان عليها الانتظار حتى تنتهي المهام الطويلة.
عند إعادة التحكّم بشكلٍ صريح، فإنّك تخبر المتصفّح بما يلي: "أعلم أنّ العمل الذي أوشك على تنفيذه قد يستغرق بعض الوقت، ولا أريد أن تضطر إلى تنفيذ كل هذا العمل قبل الاستجابة لبيانات أدخلها المستخدم أو المهام الأخرى التي قد تكون مهمة أيضًا". إنّها أداة قيّمة في مجموعة أدوات المطوّرين يمكن أن تساهم بشكلٍ كبير في تحسين تجربة المستخدم.
مشكلة استراتيجيات إعادة التحكّم الحالية
تستخدم إحدى الطرق الشائعة لإعادة التحكّم setTimeout بقيمة مهلة 0. تنجح هذه الطريقة لأنّ دالة معاودة الاتصال التي يتم تمريرها إلى setTimeout ستنقل العمل المتبقي إلى مهمة منفصلة سيتم وضعها في قائمة الانتظار لتنفيذها لاحقًا. بدلاً من انتظار أن يعيد المتصفّح التحكّم من تلقاء نفسه، فإنّك تخبره "قسّم هذه المجموعة الكبيرة من العمل إلى أجزاء أصغر".
مع ذلك، فإنّ إعادة التحكّم باستخدام setTimeout لها تأثير جانبي غير مرغوب فيه: سيتم نقل العمل الذي يأتي بعد نقطة إعادة التحكّم إلى نهاية قائمة انتظار المهام. ستظل المهام التي تمّت جدولتها من خلال تفاعلات المستخدم تنتقل إلى مقدّمة صفّ الانتظار كما ينبغي، ولكن قد يتم تأخير العمل المتبقي الذي أردت تنفيذه بعد إعادة التحكّم بشكلٍ صريح بسبب مهام أخرى من مصادر منافسة تم وضعها في صفّ الانتظار قبل هذا العمل.
لمشاهدة ذلك أثناء التنفيذ، جرِّب هذا العرض التوضيحي على Codepen أو جرِّبه في الإصدار المضمّن التالي. يتألف العرض التوضيحي من بعض الأزرار التي يمكنك النقر عليها ومربّع أسفلها يسجِّل وقت تشغيل المهام. عند الانتقال إلى الصفحة، نفِّذ الإجراءات التالية:
- انقر على الزر العلوي الذي يحمل العنوان تشغيل المهام بشكلٍ دوري، ما سيؤدي إلى جدولة المهام التي تحظر سلسلة المحادثات الرئيسية لتشغيلها بشكلٍ متكرّر. عند النقر على هذا الزر، سيتم ملء سجلّ المهام بعدة رسائل تحمل العنوان تم تشغيل مهمة تحظر سلسلة المحادثات الرئيسية باستخدام
setInterval. - بعد ذلك، انقر على الزر الذي يحمل العنوان تشغيل حلقة، مع إعادة التحكّم باستخدام
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 أثناء التنفيذ، اتّبِع الخطوات التالية:
- انتقِل إلى
chrome://flags. - فعِّل التجربة ميزات منصة الويب التجريبية. قد تحتاج إلى إعادة تشغيل Chrome بعد تنفيذ ذلك.
- انتقِل إلى صفحة العرض التوضيحي أو استخدِم الإصدار المضمّن التالي منه بعد هذه القائمة.
- انقر على الزر العلوي الذي يحمل العنوان تشغيل المهام بشكلٍ دوري.
- أخيرًا، انقر على الزر الذي يحمل العنوان تشغيل حلقة، مع إعادة التحكّم باستخدام
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:
- إذا أردت تجربة
scheduler.yieldمحليًا، اكتبchrome://flagsفي شريط عناوين Chrome واضغط على Enter، ثم اختَر تفعيل من القائمة المنسدلة في قسم ميزات منصة الويب التجريبية. سيؤدي ذلك إلى إتاحةscheduler.yield(وأي ميزات تجريبية أخرى) في مثيل Chrome الخاص بك فقط. - إذا أردت تفعيل
scheduler.yieldلمستخدمي Chromium الفعليين على مصدر متاح للجميع، عليك الاشتراك فيscheduler.yieldمرحلة تجربة وتقييم. يسمح لك ذلك بتجربة الميزات المقترَحة بأمان لفترة زمنية معيّنة، ويمنح فريق Chrome إحصاءات قيّمة حول كيفية استخدام هذه الميزات في المجال. لمزيد من المعلومات حول كيفية عمل مراحل التجربة والتقييم، اطّلِع على هذا الدليل.
تعتمد طريقة استخدام scheduler.yield، مع الاستمرار في دعم المتصفّحات التي لا تنفّذها، على أهدافك. يمكنك استخدام أداة التعبئة الرسمية. تكون أداة التعبئة مفيدة إذا انطبق ما يلي على حالتك:
- تستخدم حاليًا
scheduler.postTaskفي تطبيقك لجدولة المهام. - تريد أن تكون قادرًا على ضبط أولويات المهام وإعادة التحكّم.
- تريد أن تكون قادرًا على إلغاء المهام أو تغيير أولوياتها باستخدام فئة
TaskControllerالتي توفّرها واجهة برمجة التطبيقاتscheduler.postTask.
إذا لم ينطبق ذلك على حالتك، قد لا تكون أداة التعبئة مناسبة لك. في هذه الحالة، يمكنك إنشاء حلّ احتياطي خاص بك بطريقتَين. يستخدم النهج الأول 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، ونأمل أن تسهّل على المطوّرين تحسين الاستجابة مقارنةً باستراتيجيات إعادة التحكّم الحالية. إذا بدت لك scheduler.yield واجهة برمجة تطبيقات مفيدة، يُرجى المشاركة في بحثنا للمساعدة في تحسينها وتقديم ملاحظاتك حول كيفية تحسينها بشكلٍ أكبر.
الصورة الرئيسية من Unsplash، بعدسة Jonathan Allison.