يمكّننا المتصفّح من تتبّع تحميل الموارد الخارجيّة، مثل السكربتات والعناصر iframes والصور وما إلى ذلك، وهناك حدثان لهذا الغرض:
-
onload
-- نجاح التحميل -
onerror
-- حصل خطأ ما
تحميل سكربت
لنفترض أنّنا نودّ تحميل سكربت طرف ثالث واستدعاء دالّة موجودة فيه. يمكننا تحميله ديناميكيّا هكذا:
let script = document.createElement('script'); script.src = "my.js"; document.head.append(script);
لكن كيف يتمّ تنفيذ دالّة مصرّح بها داخل ذلك السكربت؟ علينا أن ننتظر إلى أن يُحمّل السكربت، وعندها فقط يمكننا استدعاؤها.
ملاحظة:
بالنسبة لسكربتاتنا الخاصّة يمكننا استخدام وحدات جافاسكربت لذلك، لكنّها ليست شائعة التبنّي من مكتبات الطرف الثالث.
script.onload
المساعد الرئيسيّ هو الحدث load
ويقع عند الانتهاء من تحميل السكربت وتنفيذه. على سبيل المثال:
let script = document.createElement('script'); // يمكن أن يحمّل أيّ سكربت، من أيّ نطاق script.src = "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.3.0/lodash.js" document.head.append(script); script.onload = function() { // "_" ينشئ السكربت المتغيّر alert( _.VERSION ); // shows library version };
بداخل onload
، يمكننا إذًا استخدام متغيّرات السكربت وتنفيذ الدوالّ وما إلى ذلك. لكن ماذا لو فشل التحميل؟ على سبيل المثال، إذا كان لا وجود لهذا السكربت (الخطأ 404) أو الخادم معطّل (غير متاح).
script.onerror
يمكن تتبّع الأخطاء التي تحصل خلال تحميل السكربت في حدث error
.
على سبيل المثال، لنقم بطلب سكربت غير موجود:
let script = document.createElement('script'); script.src = "https://example.com/404.js"; // لا وجود لهذا السكربت document.head.append(script); script.onerror = function() { alert("Error loading " + this.src); // https://example.com/404.js خطأ في تحميل };
يُرجى التنبّه إلى أنّه لا يمكننا الحصول على تفاصيل خطأ HTTP هنا. لا نعلم إن كان خطأ 400 أو 500 أو شيئا آخر. نعلم فقط أنّ التحميل قد فشل.
تنبيه: لا تتبّع الأحداثُ onload
/onerror
سوى التحميل نفسه.
الأخطاء التي تحصل خلال معالجة السكربت وتنفيذه خارجة عن نطاق هذه الأحداث. بمعنى: إذا حُمّل السكربت بنجاح، فإنّ onload
يشتغل، حتى لو كان في السكربت أخطاء برمجيّة. لتتبّع أخطاء السكربتات يمكن استخدام المعالج العمومي window.onerror
.
الموارد الأخرى
تعمل الأحداث load
وerror
مع الموارد الأخرى كذلك، وتعمل أساسا مع أيّ مورد له src
خارجيّ. مثلا:
let img = document.createElement('img'); img.src = "https://js.cx/clipart/train.gif"; // (*) img.onload = function() { alert(`Image loaded, size ${img.width}x${img.height}`); }; img.onerror = function() { alert("Error occurred while loading image"); };
لكنّ هناك بعض الملاحظات تعود لأسباب تاريخيّة:
-
تبدأ معظم الموارد في التحميل عندما تُضاف إلى المستند لكنّ
<img>
تُعدّ استثناءً، حيث تبدأ في التحميل عندما تحصل على الرابط أو المصدر src(*)
. -
بالنسبة لـ
<iframe>
، يقع الحدثiframe.onload
عندما ينتهي تحميل iframe، سواء بنجاح التحميل أو في حالة خطأ.
سياسة تعدد المصادر Crossorigin policy
هناك قاعدة تقول: لا يمكن لسكربتات موقع ما الوصول إلى محتويات موقع آخر. فمثلا، لا يستطيع سكربت موجود في https://facebook.com
قراءة صندوق بريد المستخدم في https://gmail.com
.
أو بشكل أدقّ، لا يمكن لأحد المصادر (الثلاثيّة domain/port/protocol البروتوكول/المنفذ/النطاق) الوصول إلى محتوى مصدر آخر. فحتى لو كان لدينا نطاق فرعيّ أو اختلف المنفذ فقط، فستُعدّ هذه مصادر مختلفة ولا يكون لديها مدخل إلى بعضها البعض.
تشمل هذه القاعدة أيضا الموارد التي هي من نطاقات أخرى.
إذا كنا نستخدم سكربتا من نطاق آخر، وكان فيه خطأ ما، فلا يمكننا الحصول على تفاصيل ذلك الخطأ. على سبيل المثال، لنأخذ السكربت (الفاسد) error.js
المكوّن من مجرّد استدعاء لدالّة:
// ? error.js noSuchFunction();
ثمّ لنحمّله من نفس الموقع الموجود فيه:
<script> window.onerror = function(message, url, line, col, errorObj) { alert(`${message}\n${url}, ${line}:${col}`); }; </script> <script src="/article/onload-onerror/crossorigin/error.js"></script>
يمكننا رؤية تقرير جيّد للخطأ، هكذا:
Uncaught ReferenceError: noSuchFunction is not defined https://javascript.info/article/onload-onerror/crossorigin/error.js, 1:1
لنحمّل الآن نفس السكربت من نطاق آخر:
<script> window.onerror = function(message, url, line, col, errorObj) { alert(`${message}\n${url}, ${line}:${col}`); }; </script> <script src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>
التقرير الآن مختلف:
Script error. , 0:0
قد تختلف التفاصيل حسب المتصفّح، لكنّ الفكرة نفسها: أيّة معلومات حول الأمور الداخليّة للسكربت، بما في ذلك مكدس آثار الخطأ error stack traces، تكون مخفيّة. يرجع ذلك بالتحديد إلى كونها من نطاق آخر.
لماذا قد نحتاج إلى تفاصيل الخطأ؟
توجد هناك العديد من الخدمات (يمكن أن نبني واحدة لنا) التي تعمل على الانصات للأخطاء العموميّة بواسطة window.onerror
، وحفظها وتوفير واجهة للوصول إليها وتحليلها. هذا جيّد، إذ يمكننا رؤية الأخطاء الحقيقيّة، التي يتسبّب فيها مستخدمونا. لكن إذا كان السكربت من مصدر آخر، فلا تكون هناك الكثير من المعلومات حول الأخطاء فيها، كما شاهدنا للتوّ.
تُفرض نفس سياسة تعدّد المصادر CORS على أنواع الموارد الأخرى كذلك. لإتاحة الوصول متعدّد المصادر، يجب أن يكون للوسم <script>
السمة crossorigin
، ويجب كذلك أن يقدّم الخادم ترويسات Headers خاصّة. هناك ثلاثة مستويات للوصول متعدّد المصادر:
-
لا وجود للسمة
crossorigin
-- الوصول ممنوع. -
crossorigin="anonymous"
-- الوصول متاح إذا أجاب الخادم بالترويسةAccess-Control-Allow-Origin
مع*
أو مع مصدرنا. لا يرسل المتصفّح معلومات الترخيص authorization وملفّات الارتباط cookies إلى الخادم البعيد. -
crossorigin="use-credentials"
-- الوصول متاح إذا أجاب الخادم بالترويسةAccess-Control-Allow-Origin
مع مصدرنا وAccess-Control-Allow-Credentials: true
. يرسل المتصفّح معلومات الترخيص وملفّات الارتباط إلى الخادم البعيد.
في حالتنا هذه، لم يكن لدينا أيّ وسم crossorigin. لذا فإنّ الوصول متعدّد المصادر قد مُنع. لنضفه الآن.
يمكننا الاختيار بين "anonymous"
(لا إرسال لملفات الارتباط، يُحتاج إلى ترويسة واحدة من جانب الخادم) و"use-credentials"
(يرسل ملفّات الارتباط أيضا، يُحتاج إلى ترويستين من جانب الخادم).
إذا لم نكن نهتمّ بملفّات الارتباط، فإنّ "anonymous"
هي الخيار:
<script> window.onerror = function(message, url, line, col, errorObj) { alert(`${message}\n${url}, ${line}:${col}`); }; </script> <script crossorigin="anonymous" src="https://cors.javascript.info/article/onload-onerror/crossorigin/error.js"></script>
حاليّا، على افتراض أنّ الخادم يوفّر ترويسة Access-Control-Allow-Origin
، كلّ شيء على ما يرام، لدينا تقرير الخطأ الكامل.
الملخص
توفّر الصور <img>
والتنسيقات الخارجيّة والسكربتات والموارد الأخرى الأحداث load
وerror
لتتبّع تحميلها:
-
يقع
load
عند نجاح التحميل. -
يقع
error
عند فشل التحميل.
الاستثناء الوحيد هو <iframe>
: لأسباب تاريخيّة يحدث load
على الدوام، عند أيّ انتهاء للتحميل، حتى لو لم توجد الصفحة.
يعمل الحدث readystatechange
أيضا مع الموارد، لكن يندر استخدامه، لأنّ الأحداث load/error
أبسط.
التمارين
حمل الصور بواسطة ردود النداء callbacks
الأهميّة: 4
عادة، تُحمّل الصور عند إنشائها. فعندما نضيف <img>
إلى الصفحة، لا يرى المستخدم الصورة فورا. يحتاج المتصفّح إلى تحميلها أوّلا.
لإظهار الصورة فورا، يمكننا إنشاؤها "مسبقا"، هكذا:
let img = document.createElement('img'); img.src = 'my.jpg';
يبدأ المتصفّح تحميل الصورة ويحفظها في الذاكرة المؤقّتة. لاحقا، عندما تحضر نفس الصورة في المستند (مهما كانت الطريقة)، فإنّها تظهر فورا.
أنشئ الدالّة preloadImages(sources, callback)
التي تحمّل جميع الصور التي في المصفوفة sources
، وعندما تجهز، تنفّذ callback
.
على سبيل المثال، سيظهر هذا التنبيه alert
بعدما تُحمّل الصور:
function loaded() { alert("Images loaded") } preloadImages(["1.jpg", "2.jpg", "3.jpg"], loaded);
حتى في حالة خطأ، يجب أن تفترض الدالّة أنّ الصورة "محمّلة". بعبارة أخرى، تُنفّذ callback
سواء عند نجاح تحميل الصور أو فشله.
تفيدنا هذه الدالّة، على سبيل المثال، عندما نريد إظهار معرض للعديد من الصور القابلة للتمرير، ونريد أن نتأكّد من أنّ جميع الصور قد حُمّلت.
ستجد في المستند المصدريّ الشيفرة وروابط لاختبار الصور، للتحقّق من أنّها قد حُمّلت. يجب أن يكون المُخرج هو 300
.
أنجز الحلّ في البيئة التجريبية
الحل
الخوارزميّة:
-
اجعل
img
لكلّ مصدر. -
أضف
onload/onerror
لكلّ صورة. -
زد في العدّاد كلّما اشتغل
onload
أوonerror
. -
عندما تصير قيمة العدّاد تساوي عدد المصادر، نكون قد انتهينا:
callback()
.
افتح الحلّ في البيئة التجريبية
ترجمة -وبتصرف- للمقال Resource loading: onload and onerror من سلسلة Browser: Document, Events, Interfaces لصاحبها Ilya Kantor
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.