navigator.sendBeacon مقابل fetch keepalive: تسليم موثوق لبيانات التحليلات دون حجب التنقل
كيفية إرسال حمولات بيانات التحليلات التي تصمد أمام تفريغ الصفحة دون تعطيل التنقل — ولماذا يهم الاختيار بين sendBeacon و fetch keepalive في جمع البيانات دون ملفات تعريف الارتباط.
تسجيل مشاهدة صفحة أو حدث أمر سهل ما دامت الصفحة حية. الجزء الصعب هو تسليم تلك الحمولة الأخيرة في اللحظة التي يغادر فيها المستخدم الصفحة، أو يغلق التبويب، أو يُرسل التطبيق إلى الخلفية على الجوال. إن أخطأت في ذلك، فإما أن تفقد البيانات أو، وهو الأسوأ، تُؤخّر التنقل نفسه الذي طلبه المستخدم. يقارن هذا المقال بين الخيارين الموثوقين — navigator.sendBeacon() و fetch() مع keepalive: true — ويوضح أيهما يستخدمه متتبع Monoid فعليًا، ولماذا، دون ملفات تعريف الارتباط أو التخزين أو بصمة الجهاز.
لماذا يُعد التفريغ حالة خاصة
عندما يبدأ المستند بالتفريغ، يكون المتصفح بصدد إنهاء عمله. تاريخيًا، أُسيء استخدام XMLHttpRequest المتزامن هنا لحجب التنقل حتى اكتمال الطلب، وهو ما يضر بالاستجابة. المتصفحات الحديثة تُثبّط هذا السلوك بنشاط، والطلبات التي تبدأ أثناء unload كثيرًا ما تُلغى. مواصفة Beacon موجودة تحديدًا لحل هذه المشكلة: فهي تتيح للصفحة جدولة طلب يضمن وكيل المستخدم محاولة إرساله بشكل غير متزامن، دون حجب تحميل الصفحة التالية.
الانعكاس العملي على التحليلات: لا تعتمد أبدًا على fetch أو XHR عادي يُطلَق من داخل معالج beforeunload/unload. فقد لا يغادر الجهاز إطلاقًا.
navigator.sendBeacon
صُمم sendBeacon خصيصًا لهذا الغرض. فهو يُدرج طلب POST صغيرًا في قائمة انتظار ويُعيد قيمة منطقية فورًا، تخبرك فقط بما إذا كان الطلب قد أُدرج بنجاح في قائمة الانتظار — لا بما إذا كان قد نجح فعليًا. بعد ذلك يُرسله المتصفح في الخلفية، حتى بعد اختفاء الصفحة.
const ok = navigator.sendBeacon('/collect', payload);
نقاط القوة:
- النقل غير مرتبط بعمر المستند.
- ذو أولوية منخفضة ومصمم بحيث لا ينافس التنقل التالي.
- لا توجد استجابة يجب معالجتها، وبالتالي لا شيء لانتظاره.
القيود، وفقًا للمواصفة وتطبيقات المتصفحات:
- POST فقط. لا يمكنك تحديد طرق تعسفية.
- تحكم محدود في الترويسات (headers). يُستنتج
Content-Typeمن نوع الحمولة (فمثلًا، يسمح لكBlobبالتأثير عليه، لكن لا يمكنك تعيين ترويسات طلب مخصصة بحرية). - تُحسب الحمولات ضمن حد بيانات يفرضه وكيل المستخدم؛ وتُعيد إشارات Beacon الكبيرة جدًا القيمة
false.
بالنسبة لتحليلات لا تعتمد على ملفات تعريف الارتباط، يكفي هذا عادةً — إذ تعتمد أدوات تتبع كثيرة على هذا الأسلوب تحديدًا ولا تعود عنه أبدًا. لا تفعل Monoid ذلك، لسبب محدد: لا يستطيع sendBeacon التعبير عن mode: 'cors'، وقطعة التثبيت الخاصة بـ Monoid تسمح للموقع بتوجيه data-api-url نحو أصل مختلف عن صفحة الاستضافة. هذا القيد الوحيد هو ما يحسم المقارنة أدناه.
fetch مع keepalive
يُعرّف معيار Fetch علامة keepalive تُبقي الطلب حيًا بعد انتهاء الصفحة التي أطلقته، ما يمنحك متانة تشبه sendBeacon مع واجهة برمجية أغنى.
fetch('/collect', {
method: 'POST',
keepalive: true,
headers: { 'Content-Type': 'application/json' },
body: payload,
});
نقاط القوة:
- تحكم كامل في الطريقة والترويسات والجسم.
- كائن
Responseحقيقي يمكنك فحصه (رغم أنه أثناء التفريغ، غالبًا لا ينبغي انتظاره).
القيود:
- تحدد مواصفة Fetch الحجم الإجمالي لجسم جميع طلبات keepalive الجارية بـ 64 كيبيبايت لكل مستند. وتجاوز هذا الحد يجعل طلب fetch مرفوضًا. وهذه ميزانية على مستوى المستند بأكمله، بحيث تتشاركها عدة طلبات keepalive متزامنة.
- تاريخيًا، تأخر دعم
keepaliveعن دعمsendBeacon، وظهرت بعض الاختلافات في التطبيق بين المتصفحات. اختبر ذلك على المتصفحات التي يستخدمها جمهورك فعليًا.
هذا بالضبط ما يستخدمه متتبع Monoid في كل طلب — /collect و /duration و /event على حد سواء:
fetch(endpoint, {
method: 'POST',
body: JSON.stringify(payload),
headers: { 'Content-Type': 'application/json' },
keepalive: true,
mode: 'cors',
});
العامل الحاسم من القسم السابق هو mode: 'cors'، لا حجم الحمولة ولا عدد الترويسات — يستحق هذا أن يُتذكّر، لأن معظم المقالات تُؤطر هذا الاختيار حول الحجم فقط.
أيهما تختار
قاعدة عملية: استخدم sendBeacon لتلك القياسات من نوع "أرسل ولا تنتظر"، حيث لا تحتاج إلى أي شيء يعود إليك، والجأ إلى fetch مع keepalive فقط عندما تحتاج فعليًا إلى ترويسات مخصصة، أو طلب مُهيكل أكبر، أو التحكم في mode عبر الأصول المختلفة. حافظ على صغر حجم الحمولات في الحالتين — أقل بكثير من ميزانية 64 كيبيبايت الخاصة بـ keepalive — لأن أحداث التحليلات ينبغي أن تكون في تصميمها أدنى ما يمكن. تقليل البيانات إلى الحد الأدنى ليس مجرد موقف امتثال؛ إنه ما يجعل التسليم الموثوق أثناء التفريغ ممكنًا أصلًا. Monoid هو الاستثناء الذي تُراعيه القاعدة أصلًا: الحمولة صغيرة والاستجابة لا تُقرأ أبدًا، وهو بالضبط النمط الذي صُمم من أجله sendBeacon — لكن دعم mode: 'cors' يرجّح الكفة.
الحدث الصحيح الذي ينبغي الاستماع إليه
لا تعتمد على unload. فهو غير موثوق ويُعطّل ذاكرة التخزين المؤقت للتنقل للأمام/للخلف (bfcache). توصي إرشادات Page Lifecycle بالاستماع إلى visibilitychange ومعاملة الانتقال إلى hidden باعتباره آخر لحظة موثوقة لتفريغ البيانات. يعمل هذا بشكل متسق عبر إغلاق سطح المكتب، وتبديل التبويبات، وإرسال التطبيق إلى الخلفية على الجوال — وهي الحالات التي لا يُطلَق فيها unload إطلاقًا.
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') flush();
});
يتبع متتبع Monoid هذا النمط: يتواجد مُستمِع visibilitychange جنبًا إلى جنب مع بديل احتياطي عبر beforeunload، بحيث يُبلَّغ عن عينة المدة سواء أُرسل التبويب إلى الخلفية على الجوال أو أُغلق تمامًا على سطح المكتب.
كيف يتلاءم هذا مع نموذج خالٍ من ملفات تعريف الارتباط
بما أن Monoid لا يقرأ أبدًا ملفات تعريف الارتباط أو التخزين ولا يقوم أبدًا ببصمة الجهاز، فإن كل طلب لا يحمل سوى ما يكشفه أصلًا — حدث خشن الحبيبات إضافة إلى الترويسات التي يرسلها المتصفح على أي حال. لا يوجد معرّف يجب الحفاظ عليه، لذا لسنا بحاجة أبدًا إلى تبادل ثنائي الاتجاه لمزامنة الحالة. هذا بالضبط ما يجعل fetch مع keepalive الخيار السهل بمجرد أن يُخرج mode: 'cors' خيار sendBeacon من المعادلة: طلب POST يُرسَل مرة واحدة ولا نفحص استجابته أبدًا ليس قيدًا بالنسبة لنا؛ بل هو ما يوائم بين موثوقية التسليم والخصوصية بحكم التصميم.
Comments
Loading comments…