واجهة برمجة تطبيقات JavaScript جديدة قد تساعدك في تجنُّب المفاضلة بين أداء التحميل وسرعة استجابة الإدخال
هناك صعوبة في التحميل السريع. يجب على المواقع الإلكترونية التي تستخدم JavaScript لعرض المحتوى في الوقت الحالي إجراء مفاضلة بين أداء التحميل واستجابة الإدخال: إما أن تنفِّذ كل الأعمال اللازمة للعرض في وقت واحد (أداء أفضل للتحميل أو قدرة أكبر على الاستجابة للإدخال)، أو أن تقسّم العمل إلى مهام أصغر حجمًا لتحافظ على استجابة الإدخال وسرعة الاستجابة (أداء تحميل أسوأ واستجابة أفضل للإدخال).
ولتجنُّب الحاجة إلى إجراء هذا التوازن، اقترح Facebook واجهة برمجة التطبيقات isInputPending()
ونفّذها في Chromium لتحسين الاستجابة بدون
الاستسلام. استنادًا إلى الملاحظات التي تردنا حول مرحلة التجربة والتقييم، أجرينا عددًا من التعديلات على
واجهة برمجة التطبيقات، ويسرّنا الإعلان عن أنّ واجهة برمجة التطبيقات يتم الآن الشحن تلقائيًا في Chromium
87.
توافُق المتصفح
isInputPending()
يتم تضمينها في المتصفّحات المستندة إلى Chromium بدءًا من الإصدار 87.
لم يُظهر أي متصفّح آخر نية لطرح واجهة برمجة التطبيقات.
الخلفية
يتم تنفيذ معظم الأعمال في منظومة JavaScript المتكاملة في الوقت الحالي من خلال سلسلة محادثات واحدة: سلسلة التعليمات الرئيسية. يوفّر ذلك نموذج تنفيذ قويًا للمطوّرين، ولكن يمكن أن تتأثر تجربة المستخدم (الاستجابة تحديدًا) بشكل كبير في حال تنفيذ النص البرمجي لفترة طويلة. على سبيل المثال، إذا كانت الصفحة تبذل جهدًا كبيرًا أثناء تنشيط حدث إدخال، لن تتعامل الصفحة مع حدث إدخال النقر إلا بعد اكتمال ذلك العمل.
أفضل الممارسات الحالية هي التعامل مع هذه المشكلة عن طريق تقسيم JavaScript إلى كتل أصغر. أثناء تحميل الصفحة، يمكن للصفحة تنفيذ برمجة برمجة JavaScript، ثم تسليم التحكّم وإعادته إلى المتصفّح. يمكن للمتصفّح بعد ذلك التحقّق من قائمة انتظار أحداث الإدخال لمعرفة ما إذا كان هناك أيّ حدث يحتاج إلى إبلاغ الصفحة به. بعد ذلك، يمكن للمتصفّح العودة إلى تشغيل ملفّات برمجة JavaScript عند إضافتها. يساعد ذلك، ولكن يمكن أن يؤدي إلى مشاكل أخرى.
في كل مرة تُعيد فيها الصفحة التحكّم إلى المتصفّح، يستغرق المتصفّح بعض الوقت للتحقّق من قائمة أحداث الإدخال ومعالجة الأحداث واختيار كتلة JavaScript التالية. وفي حين يستجيب المتصفّح للأحداث بشكل أسرع، يتباطأ بشكل عام وقت تحميل الصفحة. وإذا خفضنا معدّل الاستجابة كثيرًا، سيتم تحميل الصفحة ببطء شديد. وإذا قلّ معدّل النتائج، سيستغرِق المتصفّح وقتًا أطول للاستجابة إلى أحداث المستخدمين، ما يزعج المستخدمين. غير ممتع.
في Facebook، أردنا معرفة كيف سيكون الوضع إذا توصّلنا إلى أسلوب loading جديد يزيل هذا الخيار الصعب. لذلك،
تواصلنا مع فريق Chrome بشأن هذا الأمر، وطرحنا الاقتراح التالي:
isInputPending()
. واجهة برمجة التطبيقات isInputPending()
هي أوّل واجهة تستخدم مفهوم
المقاطعات لإدخالات المستخدمين على الويب، وتسمح لواجهة JavaScript
بالتحقّق من الإدخالات بدون التسليم للمتصفّح.
ونظرًا لاهتمامنا بواجهة برمجة التطبيقات، عقدنا شراكة مع زملائنا في Chrome لتنفيذ الميزة وشحنها في Chromium. وبمساعدة مهندسي Chrome، تم طرح الإصلاحات بعد مرحلة التجربة والتقييم (وهي طريقة يتّبعها Chrome لاختبار التغييرات والحصول على ملاحظات من المطوّرين قبل طرح واجهة برمجة التطبيقات بالكامل).
لقد تلقّينا الآن ملاحظات من مرحلة التجربة والتقييم ومن الأعضاء الآخرين في مجموعة عمل W3C Web Performance Working وأجرينا تغييرات على واجهة برمجة التطبيقات.
مثال: جدولة مُعدّل العائد
لنفترض أنّ لديك الكثير من الأعمال التي تؤدي إلى حظر العرض لتحميل
صفحتك، مثل إنشاء ترميز من المكوّنات أو استبعاد العناصر الأساسية أو
مجرد رسم مؤشر تحميل رائع. ويتم تقسيم كلّ منها إلى
عنصر عمل منفصل. باستخدام نمط أداة الجدولة، لنرسم كيف يمكننا معالجة عملنا في دالة processWorkQueue()
افتراضية:
const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
if (performance.now() >= DEADLINE) {
// Yield the event loop if we're out of time.
setTimeout(processWorkQueue);
return;
}
let job = workQueue.shift();
job.execute();
}
من خلال استدعاء processWorkQueue()
لاحقًا في مهمة ماكرو جديدة من خلال setTimeout()
، نمنح المتصفّح إمكانية الاستمرار في الاستجابة إلى حدّ ما للإدخال (يمكنه تنفيذ معالجات الأحداث قبل استئناف العمل) مع مواصلة العمل بدون انقطاع نسبيًا. ومع ذلك، قد يتم إلغاء جدولة المهام لفترة طويلة بسبب مهام أخرى
تريد التحكّم في حلقة الأحداث، أو قد تزيد مدة استجابة الأحداث إلى QUANTUM
ملي ثانية إضافية.
هذا جيد، ولكن هل يمكننا تحسين الأداء؟ بكل تأكيد.
const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
if (navigator.scheduling.isInputPending() || performance.now() >= DEADLINE) {
// Yield if we have to handle an input event, or we're out of time.
setTimeout(processWorkQueue);
return;
}
let job = workQueue.shift();
job.execute();
}
من خلال تقديم طلب إلى navigator.scheduling.isInputPending()
، يمكننا
الردّ على الإدخال بشكل أسرع مع ضمان تنفيذ عملية حظر الشاشة
بدون انقطاع. إذا لم نكن مهتمين بمعالجة أي ملف
بخلاف الإدخال (مثل الرسم) إلى أن يكتمل العمل، يمكننا بسهولة زيادة
طول QUANTUM
أيضًا.
لا يتم تلقائيًا عرض الأحداث "المستمرة" من isInputPending()
. وتشمل هذه التطبيقات
mousemove
وpointermove
وغيرها. إذا كنت مهتمًا بالحصول على
هذه أيضًا، فلا توجد مشكلة. من خلال تقديم عنصر إلى isInputPending()
مع تحديد
includeContinuous
على true
، يمكننا المتابعة:
const DEADLINE = performance.now() + QUANTUM;
const options = { includeContinuous: true };
while (workQueue.length > 0) {
if (navigator.scheduling.isInputPending(options) || performance.now() >= DEADLINE) {
// Yield if we have to handle an input event (any of them!), or we're out of time.
setTimeout(processWorkQueue);
return;
}
let job = workQueue.shift();
job.execute();
}
هذا كل شيء! تعمل أطر العمل مثل React على توفير دعم isInputPending()
في مكتبات الجدولة الأساسية لديها باستخدام منطق مشابه. نأمل أن يؤدي ذلك إلى تمكّن المطوّرين الذين يستخدِمون هذه الأطر من الاستفادة من isInputPending()
من وراء الكواليس بدون إجراء عمليات إعادة كتابة كبيرة.
التراجع ليس سيئًا دائمًا
تجدر الإشارة إلى أنّ تحقيق عائد أقل ليس الحل المناسب لكل حالة استخدام. هناك العديد من الأسباب التي تدفعك إلى إعادة التحكّم إلى المتصفّح بخلاف معالجة أحداث الإدخال، مثل تنفيذ العرض وتنفيذ نصوص برمجية أخرى في الصفحة.
هناك حالات يتعذّر فيها على المتصفّح تحديد مصدر أحداث الإدخال
المعلَّقة بشكل صحيح. وعلى وجه الخصوص، قد يؤدي ضبط مقاطع وأقنعة معقّدة لإطارات div في صفحات ويب من مصادر مختلفة إلى الإبلاغ عن نتائج خاطئة (أي قد يعرض isInputPending()
بشكل غير متوقّع قيمة "خطأ" عند استهداف هذه الإطارات). تأكَّد من أنّك تُجري عمليات عرض بشكلٍ كافٍ إذا كان
موقعك الإلكتروني يتطلّب تفاعلات مع إطارات فرعية منمّقة.
انتبه أيضًا إلى الصفحات الأخرى التي تشارك حلقة أحداث. على الأنظمة الأساسية مثل Chrome لنظام Android، من الشائع أن تشارك أصول متعددة حلقة حدث. لن تعرض isInputPending()
أبدًا القيمة true
إذا تم إرسال الإدخال إلى إطار من مصدر مختلف، وبالتالي قد تتداخل الصفحات التي تعمل في الخلفية مع سرعة استجابة الصفحات التي تعمل في المقدّمة. قد تحتاج إلى تقليل الوقت أو تأجيله أو تحقيق الربح بمعدّل أكبر عند العمل في الخلفية باستخدام واجهة برمجة تطبيقات مستوى رؤية الصفحة.
ننصحك باستخدام isInputPending()
بحذر. وإذا لم يكن هناك عمل مطلوب من المستخدمين
لحظر المستخدمين، فكن لطيفًا مع الآخرين في حلقة الحدث من خلال
التنفيذ مرات أكثر. قد تكون المهام الطويلة ضارّة.
ملاحظات
- يمكنك إرسال ملاحظاتك حول المواصفات في مستودع is-input-pending.
- يمكنك التواصل مع @acomminos (أحد مؤلفي المواصفات) على Twitter.
الخاتمة
يسرّنا إطلاق isInputPending()
، ويسرّنا أيضًا أنّه يمكن للمطوّرين
بدء استخدامه اليوم. هذه هي المرة الأولى التي تُنشئ فيها Facebook
واجهة برمجة تطبيقات جديدة للويب وتنقلها من مرحلة حضانة الأفكار إلى اقتراح المعايير ثم إلى مرحلة الطرح في المتصفّح. نشكر جميع من ساعدنا في الوصول إلى هذا الإنجاز، ونشكر بشكل خاص جميع موظفي Chrome الذين ساعدونا في تطوير هذه الفكرة وطرحها.
الصورة الرئيسية تقدّمها Will H McMahan على Unsplash.