تطرقنا سابقًا للحديث عن واجهة برمجة التطبيقات واستخدامها في مهام مختلفة مثل معالجة مستندات الويب وشجرة DOM، ومن المهام اﻷخرى الشائعة لتطبيقات ومواقع الويب إحضار بيانات محددة من الخادم لتحديث بعض أجزاء صفحة الويب دون الحاجة إلى تحميل صفحة جديدة. وكان لهذا اﻷمر البسيط أثر هائل على أداء وسلوك صفحات الويب. سنشرح في هذا المقال التقنيات التي تسمح بتنفيذ هذه اﻷمور مثل وعلى وجه الخصوص الواجهة Fetch
.
ننصحك قبل المتابعة في قراءة هذه المقالات أن:
- تكون ملمًا بلغتي HTML و CSS.
- تكون ملمًا بلغة جافا سكريبت.
- تطلع على سلاسل المقالات السابقة التي ناقشت أساسيات جافا سكريبت والكائنات في جافا سكريبت.
- تطلع على أساسيات الواجهات البرمجية في طرف العميل.
ما الذي يحدث عند طلب مورد من الخادم؟
تتكون صفحة الويب من صفحة HTML إضافة إلى عدة ملفات أخرى كملفات التنسيق CSS والسكريبتات والصور. وما يحدث وفق النموذج النمطي لتحميل الصفحة، أن متصفحك سيرسل عدة طلبات إلى الخادم لإحضار تلك الملفات اللازمة لعرض الصفحة بالشكل المطلوب، ومن المفترض أن يستجيب الخادم مرسلًا تلك الملفات. ويتكرر اﻷمر في كل مرة تزور فيها صفحة جديدة.
يعمل هذا النموذج جيدًا في العديد من المواقع، لكن ماذا لو كان الموقع مخصصًا ﻹحضار بيانات من الخادم مثل موقع مكتبة؟ ومن الممكن أن تفكّر في هذا النوع من المواقع على أنه واجهة تربط المستخدم بقاعدة بيانات. فقد يسمح لك بالبحث عن نوع معين من الكتب أو قد ينصحك بكتب تستهويك وفقًا لقائمة الكتب التي استعرتها سابقًا. وعندما تفعل ذلك، لا بد من تحديث محتوى الصفحة بمجموعة الكتب الجديدة التي بحثت عنها أو نصحك بها الموقع. مع هذا، سيبقى جُل محتوى الصفحة كما هو دون تغيير مثل الترويسة والتذييل والأشرطة الجانبية.
إن مشكلة النموذج النمطي في هذه الحالة هو أنه سيحضر البيانات ويعيد تحميل الصفحة بأكملها حتى لو أردنا تحديث جزء صغير منها، وهذا أمر عديم الجدوى كما يقدم تجربة سيئة لمستخدمي الموقع. ولتجنب هذا اﻷمر، تستخدم العديد من مواقع الويب واجهات جافا سكريبت البرمجية APIs لطلب البيانات من الخادم وتحديث محتوى الصفحة دون إعادة تحميلها. فعندما يبحث المستخدم عن منتج جديد مثلًا، سيطلب المتصفح فقط البيانات التي يحتاجها لتحديث الصفحة كأن يطلب قائمة بالكتب التي يجب عرضها.
إن الواجهة البرمجية الرئيسية في هذه الحالة هي الواجهة Fetch التي تسمح لشيفرة جافا سكريبت في صفحة الويب بإرسال طلبات إلى الخادم لإحضار مورد محدد. وعندما يقدم الخادم البيانات المطلوبة، يمكن للشيفرة أن تستخدمها لتحديث محتوى الصفحة من خلال واجهة برمجية أخرى هي شجرة DOM عادة. وغالبًا ما تكون البيانات المطلوبة محضّرة وفق تنسيق JSON وهي صيغة مناسبة لنقل البيانات المهيكلة، لكن البيانات قد تكون أيضًا شيفرة HTML أو مجرد نص نمطي. وستجد هذا النموذج في الكثير من العديد من المواقع المصممة لتبادل البيانات مثل أمازون ويوتيوب وإي باي وغيرها. ومن خلال هذا النموذج:
- سيكون تحديث الصفحة أسرع بكثير ولن تُضطر إلى الانتظار حتى تُحمَّل الصفحة ككل، وهذا ما يعطي شعورًا بان الموقع أسرع وأكثر تجاوبًا.
- يُنزَّل كم قليل من البيانات عند كل تحديث للمحتوى وبالتالي هدرًا أقل لحزمة البيانات المخصصة للمستخدم. وعلى الرغم أن هذا الأمر لن يكون مشكلة في اﻷجهزة المكتبية المتصلة بحزمة اتصال عريضة broadband لكنها مهمة جدًا في اﻷجهزة المحمولة أو في الدول التي لا تمتلك خدمة انترنت سريعة.
ملاحظة: عُرفت هذه التقنية في بداياتها باسم "جافا سكريبت غير المتزامنة و XML" واختصارًا أجاكس Ajax لأنها تميل إلى إحضار البيانات على شكل بيانات XML. وعلى الرغم أن البيانات المطلوبة حاليًا هي بيانات JSON، لكن الطريقة تبقى ذاتها ولازال المصطلح Ajax يشير إلى هذه التقنية
ولتسريع اﻷمر أكثر، تُخزّن بعض المواقع أيضًا بيانات وأصولًا ضمن حاسوب المستخدم عند طلبها للمرة اﻷولى، وبالتالي ستُستخدم هذه النسخة المخزنة عند الزيارات اللاحقة بدلًا من تنزيل نسخ جديدة في كل مرة تُحمّل فيها الصفحة. ولن يُعاد تحميل المحتوى من الخادم إلا عندما يُحدّث هذا المحتوى على الخادم.
الواجهة البرمجية Fetch
سنتعلم أكثر عن هذه الواجهة من خلال المثاليين اﻵتيين.
إحضار محتوى نصي
سنطلب في هذا المثال بيانات نصية من عدة ملفات ونستخدمها لتحديث الجزء الذي يضم محتوى الصفحة. ستعمل سلسلة الملفات النصية السابقة كقاعدة بيانات مفترضة، لكن تجدر اﻹشارة إلا أننا نستخدم لطلب البيانات عادة لغة برمجة من طرف الخادم في حالات كهذه مثل PHP أو بايثون أو Node.js من قاعدة بيانات حقيقية. مع ذلك، نتوخى في مثالنا البساطة ونحاول التركيز على تقنية إحضار البيانات من طرف العميل.
وحتى تبدأ العمل معنا حمّل نسختك من الملفات fetch-start.html و verse1.txt و verse2.txt و verse3.txt verse4.txt، ثم ضعها في مجلد جديد على حاسوبك. وما سنفعله لاحقًا هو إحضار أبيات محددة من قصيدة عندما نختار هذه اﻷبيات من قائمة منسدلة.
أضف الشيفر التالية ضمن العنصر <script>
. إذ تُخزّن هذه الشيفرة مرجعين إلى العنصرين <select>
و <pre>
، وعندما يختار المستخدم قيمةً، تمرر هذه القيمة كمعامل إلى الدالة ()updateDisplay
:
const verseChoose = document.querySelector("select"); const poemDisplay = document.querySelector("pre"); verseChoose.addEventListener("change", () => { const verse = verseChoose.value; updateDisplay(verse); });
لنعرّف بداية الدالة ()updateDisplay
بوضع الشيفرة التالية تحت الشيفرة السابقة:
function updateDisplay(verse) { }
نبدأ كتابة شيفرة الدالة بإنشاء عنوان URL نسبي يشير إلى الملف النصي الذي نريد تحميله، إذ نحتاجه لاحقًا. وستكون قيمة العنصر <select>
مطابقة دائمًا لمحتوى هذا العنصر ما لم نسند إليه قيمة أخرى من خلال السمة value
مثل "Verse 1". في هذه الحالة سيكون الملف النصي الموافق هو الملف "verse1.txt" الموجود في نفس المجلد الذي يضم ملف HTML، لهذا يكفي استخدام اسم الملف كعنوان URL نسبي.
وانتبه إلى أن الخوادم تتحس حالة اﻷحرف غالبًا لهذا السبب علينا إزالة الفراغ من القيمة "Verse 1" وكذلك تحويل الحرف "V" إلى الشكل الصغير "v" ومن ثم إضافة اللاحقة "txt.". ولتنفيذ ذلك، استخدم التابعين النصيين ()replace
و ()toLowerCase
إضافة إلى قالب حرفي template literal {...}$
كالتالي:
verse = verse.replace(" ", "").toLowerCase(); const url = `${verse}.txt`;
وأخيرًا أصبحنا مستعدين لاستخدام الواجهة البرمجية Fetch:
//URL ومرر إليها عنوان `fetch()`استدع fetch(url) //وعدًا، وعندما يستجيب الخادم يًستدعى التابع fetch() تعيد الدالة //`then()` .then((response) => { // يرمي معالج الحدث خطأً إن أخفق الوعد if (!response.ok) { throw new Error(`HTTP error: ${response.status}`); } // وإن نحج الوعد، يعيد معالج الحدث الاستجابة على شكل نص باستدعاء التابع //الذي يعيد بدوره وعدًا، وعند إنجاز الوعد اﻷخير response.text() }) //`poemDisplay` الذي يعيد النص فننسخه إلى مربع النص `then()` يُستدعى .then((text) => { poemDisplay.textContent = text; }) //`poemDisplay` التقاط أية أخطاء أخرى وعرضها برسالة ضمن الصندوق .catch((error) => { poemDisplay.textContent = `Could not fetch verse: ${error}`; });
وإليك توضيحًا للشيفرة السابقة:
-
إن مدخل الواجهة البرمجية Fetch هو الدالة العامة
()fetch
التي تأخذ عنوان URL معاملًا لها (ولها أيضًا معامل آخر اختياري لأغراض خاصة، لكننا لم نستخدمه). -
الدالة
()fetch
هي دالة غير متزامنة تعيد وعدًاPromise
، بإمكانك مراجعة مقال استخدام الوعود في جافا سكريبت غير المتزامنة إن لم تكن على دراية بمفهوم الوعود، ثم العودة والمتابعة معنا، وستلاحظ أن هذا المقال يتحدث أيضًا عن الواجهة Fetch. -
تعيد الدالة
()fetch
وعدًا، لهذا نمرر دالة إلى التابع()then
المرتبط بهذا الوعد. يُستدعى هذا التابع حالما يتلقى طلب HTTP ردًا من الخادم. ومن ثم نتحقق من نجاح الوعد (إنجازه) ضمن دالة معالج الحدث ونرمي رسالة خطأ إن لم ينجح. وعند النجاح، نستدعي الدالة()response.text
للحصول على جسم الاستجابة على شكل نص. -
إن الدالة
()response.text
هي أيضًا دالة غير متزامنة، لهذا نعيد الوعد الذي تعيده ونمرره إلى التابع()then
المرتبطة بالوعد الجديد. يُستدعى هذا التابع عندما يجهز نص الاستجابة، ونضع ضمنه شيفرة تحديث محتوى العنصر<pre>
. -
نربط أخيرًا دالة المعالجة
()catch
في النهاية لالتقاط أية أخطاء ترميها أيًا من الدوال غير المتزامنة التي استدعيناها أو معالجات اﻷحداث المتعلقة بها.
أحد مشكلات هذا المثال أنه لن يعرض أية قصيدة عندما يُحمّل للمرة الأولى. وﻹصلاح اﻷمر، أضف السطرين التاليين في نهاية شيفرتك (قبل وسم النهاية <script/>
) لتحميل verse 1 افتراضيًا ولكي يأخذ العنصر <select>
القيمة الصحيحة.
updateDisplay("Verse 1"); verseChoose.value = "Verse 1";
تشغيل المثال على الخادم
لن تسمح المتصفحات الحديثة بتنفيذ طلبات HTTP إن كنت تشغّل المثال على حاسوبك الشخصي بسبب قيود أمنية. وللالتفاف على الموضوع، عليك اختبار المثال على خادم ويب محلي. للمزيد من المعلومات اطلع على مقال دليل إعداد خادم ويب محلي خطوة بخطوة الذي نشرته أكاديمية حسوب.
متجر معلبات
أنشأنا في هذا المثال موقعًا بسيطًا يُدعى متجر المعلبات The Can Store، وهو سوبر ماركت يبيع المعلبات فقط. بإمكانك تجربة المثال مباشرة على جت-هب والاطلاع على شيفرته المصدرية.
يعرض هذا الموقع افتراضيًا جميع المنتجات، لكنك تستطيع ترشيح أو فلترة هذه المنتجات والبحث عنها باستخدام أدوات التحكم الموجودة ضمن العمود اليساري للصفحة.
قد تجد بعض التعقيد في شيفرة ترشيح المنتجات وفقًا للتصنيف مثل معايير البحث ومعالجة النصوص لعرض البيانات بشكل صحيح على واجهة المستخدم وغيرها. لن نشرح بالطبع كل التفاصيل في هذا المقال لكنك ستجد كما كبيرًا من التعليقات التي تشرح الشيفرة ضمن الملف can-script.js، مع ذلك سنشرح شيفرة الواجهة fetch.
ستجد أولى الكتل البرمجية التي تستخدم fetch في مقدمة الملف:
fetch("products.json") .then((response) => { if (!response.ok) { throw new Error(`HTTP error: ${response.status}`); } return response.json(); }) .then((json) => initialize(json)) .catch((err) => console.error(`Fetch problem: ${err.message}`));
تعيد الدالة ()fetch
وعدًا، فإن أنجز هذا الوعد بنجاح، ستعاد الدالة الموجودة ضمن أول كتلة ()then
تضم الاستجابة response
من الشبكة، وما نفعله ضمن هذه الدالة هو:
-
التحقق من عدم إرسال الخادم خطأً (مثل
Not Found 404
) وإن حدث ذلك، نرمي الخطأ. -
استدعاء التابع
()json
للعمل على الاستجابة واستخلاص البيانات منها على شكل كائن JSON، ثم نعيد الوعد الذي يعيدهresponse.json
.
نمرر دالة إلى التابع ()then
المرتبط بالوعد المعاد، كما نمرر إلى هذه الدالة كائنًا يتضمن بيانات الاستجابة وفق تنسيق JSON بعد تمريرها إلى الدالة ()initialize
التي تبدأ عملية عرض جميع المنتجات على واجهة المستخدم.
ولمعالجة اﻷخطاء، نربط كتلة ()catch.
في نهاية السلسلة، وستعمل شيفرة هذه الكتلة إذا وقع خطأ لسبب ما. نضع ضمن هذه الكتلة دالة يُمرر إليها الكائن err
كمعامل ويُستخدم لتسجيل طبيعة الخطأ الذي حصل ونعرضه من خلال الدالة ()console.error
.
تتعامل المواقع المكتملة مع اﻷخطاء بطريقة شمولية أفضل، وذلك بعرض رسالة على شاشة المستخدم. كما قد تعرض أيضًا خيارات لحل المشكلة، لكننا لن نحتاج هنا إلا للتابع ()console.error
.
بإمكانك أيضًا اختبار حالات الفشل بنفسك:
- انسخ ملفات التمرين على حاسوبك.
- شغل الشيفرة باستخدام خادم ويب محلي.
- عدّل مسار الملف الذي نحضره مثل "produc.json" بدلًا من "product.json" (تأكد من ارتكاب خطأ كتابي).
-
حمّل اﻵن الملف index.html في المتصفح (
localhost:8000/index.html
) ثم ألق نظرة على طرفية جافا سكريبت، وستجد رسالة خطأ مشابهة للرسالة "Fetch problem: HTTP error: 404".
ستجد كتلة fetch الثانية ضمن الدالة ()fetchBlob
:
fetch(url) .then((response) => { if (!response.ok) { throw new Error(`HTTP error: ${response.status}`); } return response.blob(); }) .then((blob) => showProduct(blob, product)) .catch((err) => console.error(`Fetch problem: ${err.message}`));
تعمل هذه الشيفرة تمامًا كسابقتها ما عدا أننا استخدمنا التابع ()blob
بدلًا من ()json
لأننا نريد في هذه الحالة الحصول على ملف صورة في الاستجابة وسيكون حينها تنسيق البيانات على شكل كائن بيانات ثنائية Blob (اختصارًا للعبارة "كائن ضخم ثنائي Binry Large Object"). ويُستخدم هذا الكائن لتمثيل كائنات ضخمة مشابهة للملفات مثل الصور والفيديو.
وبمجرد أن نحصل على الكائن blob، نمرره إلى الدالة ()showProduct
التي تعرضه.
الواجهة البرمجية XMLHttpRequest
سترى في بعض اﻷحيان وخاصة في الشيفرة اﻷقدم واجهة برمجية أخرى تُدعى XMLHttpRequest
(وتختصر أحيانًا إلى "XHR") تُستخدم في إجراء طلبات HTTP. وقد سبقت هذه الواجهة الواجهة البرمجية Fetch وكانت أولى الواجهات التي استخدمت على نطاق واسع لتنفيذ تقنية AJAX. لكن ننصحك باستخدام Fetch إن أمكن فهي واجهة أبسط وتضم ميزات أكثر من الواجهة XMLHttpRequest. لن نقدم مثالًا عن استخدام الواجهة XMLHttpRequest
، لكننا سنعرض نسخة XMLHttpRequest
من مثال متجر المعلبات. سيبدو الطلب كالتالي:
const request = new XMLHttpRequest(); try { request.open("GET", "products.json"); request.responseType = "json"; request.addEventListener("load", () => initialize(request.response)); request.addEventListener("error", () => console.error("XHR error")); request.send(); } catch (error) { console.error(`XHR error ${request.status}`); }
هناك خمس مراحل:
-
إنشاء كائن
XMLHttpRequest
. جديد. -
استدعاء التابع
()open
لتهيئة الكائن الجديد. -
إضافة مترصد للحدث
load
يُنفَّذ عند إكتمال الطلب بنجاح. ونستدعي الدالة()initialize
بعد تزويدها بالبيانات ضمن دالة مترصد الحدث. -
إضافة مترصد حدث إلى الحدث
error
الذي يقع عندما يواجهة الطلب خطأً. - إرسال الطلب
ولا بد من تغليف الشيفرة السابقة ضمن كتلة try...catch
للتعامل مع الأخطاء التي قد تحدث عند استخدام التابعين ()open
أو ()send
.
ومن الجيد أن تدرك أن الواجهة Fetch هي تطوير للواجهة XMLHttpRequest، وأن تفهم الطريقة المتبعة في التعامل مع اﻷخطاء في كلتا الواجهتين.
الخلاصة
شرحنا في هذا المقال طريقة استخدام الواجهة البرمجية Fetch في إحضار البيانات من الخادم، وسنتناول بعض المواضيع التي وردت في المقال بمزيد من التفصيل في مقالاتنا التالية.
ترجمة-وبتصرف- للمقال: Fetching data from the server.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.