ابدأ مع Node: مقدمة لواجهات برمجة التطبيقات و HTTP و ES6 + JavaScript
نشرت: 2022-03-10ربما تكون قد سمعت عن Node.js على أنه "وقت تشغيل JavaScript غير متزامن مبني على محرك Chrome's V8 JavaScript" ، وأنه "يستخدم نموذج إدخال / إخراج يحركه الحدث ولا يحظره مما يجعله خفيف الوزن وفعال". لكن بالنسبة للبعض ، هذا ليس أعظم التفسيرات.
ما هي العقدة في المقام الأول؟ ماذا يعني بالضبط أن تكون العقدة "غير متزامنة" ، وكيف يختلف ذلك عن "متزامن"؟ ما هو معنى "مدفوعة بالحدث" و "عدم الحظر" على أي حال ، وكيف تتلاءم العقدة مع الصورة الأكبر للتطبيقات وشبكات الإنترنت والخوادم؟
سنحاول الإجابة على كل هذه الأسئلة والمزيد خلال هذه السلسلة حيث نلقي نظرة متعمقة على الأعمال الداخلية للعقدة ، ونتعرف على بروتوكول نقل النص التشعبي ، وواجهات برمجة التطبيقات ، و JSON ، ونبني Bookshelf API الخاص بنا باستخدام MongoDB و Express و Lodash و Mocha و المقاود.
ما هو Node.js
العقدة ليست سوى بيئة ، أو وقت تشغيل ، يمكن من خلالها تشغيل JavaScript عادي (مع اختلافات طفيفة) خارج المتصفح. يمكننا استخدامه لبناء تطبيقات سطح المكتب (مع أطر مثل Electron) ، وكتابة خوادم الويب أو التطبيقات ، والمزيد.
الحظر / عدم الحظر والمتزامن / غير المتزامن
لنفترض أننا نجري استدعاء قاعدة بيانات لاسترداد خصائص مستخدم. سيستغرق هذا الاتصال بعض الوقت ، وإذا كان الطلب "محظورًا" ، فهذا يعني أنه سيحظر تنفيذ برنامجنا حتى تكتمل المكالمة. في هذه الحالة ، قدمنا طلبًا "متزامنًا" حيث انتهى الأمر بحظر مؤشر الترابط.
لذلك ، تؤدي العملية المتزامنة إلى حظر عملية أو مؤشر ترابط حتى تكتمل تلك العملية ، مما يترك الخيط في "حالة انتظار". العملية غير المتزامنة ، من ناحية أخرى ، لا تمنع . يسمح بتنفيذ مؤشر الترابط للمتابعة بغض النظر عن الوقت الذي تستغرقه العملية لإكمالها أو النتيجة التي تكتمل بها ، ولا يقع أي جزء من مؤشر الترابط في حالة انتظار في أي وقت.
لنلقِ نظرة على مثال آخر لاستدعاء متزامن يحظر مؤشر ترابط. لنفترض أننا نبني تطبيقًا يقارن نتائج اثنين من واجهات برمجة تطبيقات Weather لإيجاد النسبة المئوية للاختلاف في درجة الحرارة. بطريقة الحظر ، نقوم بإجراء مكالمة إلى Weather API One وننتظر النتيجة. بمجرد أن نحصل على نتيجة ، نطلق على Weather API Two وننتظر نتيجتها. لا تقلق في هذه المرحلة إذا لم تكن معتادًا على واجهات برمجة التطبيقات. سنقوم بتغطيتها في القسم القادم. في الوقت الحالي ، فكر فقط في واجهة برمجة التطبيقات على أنها الوسيط الذي يمكن من خلاله أن يتواصل جهازي كمبيوتر مع بعضهما البعض.
اسمح لي أن أشير إلى أنه من المهم إدراك أنه ليست كل المكالمات المتزامنة محظورة بالضرورة. إذا تمكنت عملية متزامنة من الإكمال دون حظر مؤشر الترابط أو التسبب في حالة انتظار ، فهذا يعني أنها غير محظورة. في معظم الأوقات ، سيتم حظر المكالمات المتزامنة ، وسيعتمد الوقت الذي تستغرقه حتى تكتمل على مجموعة متنوعة من العوامل ، مثل سرعة خوادم واجهة برمجة التطبيقات ، وسرعة تنزيل اتصال الإنترنت للمستخدم النهائي ، وما إلى ذلك.
في حالة الصورة أعلاه ، كان علينا الانتظار بعض الوقت لاسترداد النتائج الأولى من API One. بعد ذلك ، كان علينا الانتظار بنفس القدر للحصول على استجابة من API الثاني. أثناء انتظار كلا الردين ، قد يلاحظ المستخدم تعليق تطبيقنا - ستغلق واجهة المستخدم حرفيًا - وسيكون ذلك أمرًا سيئًا لتجربة المستخدم.
في حالة وجود مكالمة غير محظورة ، سيكون لدينا شيء مثل هذا:
يمكنك أن ترى بوضوح مدى سرعة الانتهاء من التنفيذ. بدلاً من الانتظار في API One ثم الانتظار في API الثانية ، يمكننا الانتظار حتى يكتمل كلاهما في نفس الوقت وتحقيق نتائجنا أسرع بنسبة 50٪ تقريبًا. لاحظ أنه بمجرد استدعاء API One وبدأنا في انتظار ردها ، أطلقنا أيضًا على API Two وبدأنا في انتظار ردها في نفس الوقت مثل One.
في هذه المرحلة ، قبل الانتقال إلى المزيد من الأمثلة الملموسة والملموسة ، من المهم الإشارة إلى أنه ، من أجل السهولة ، يتم اختصار مصطلح "متزامن" بشكل عام إلى "مزامنة" ، ويتم اختصار مصطلح "غير متزامن" بشكل عام إلى "غير متزامن". سترى هذا الترميز مستخدمًا في أسماء الطرق / الوظائف.
وظائف رد الاتصال
قد تتساءل ، "إذا كان بإمكاننا التعامل مع مكالمة غير متزامنة ، فكيف نعرف متى تنتهي هذه المكالمة ولدينا استجابة؟" بشكل عام ، نقوم بتمرير دالة رد نداء كوسيطة لطريقتنا غير المتزامنة ، وستقوم هذه الطريقة "باستدعاء" هذه الوظيفة في وقت لاحق مع استجابة. أنا أستخدم وظائف ES5 هنا ، لكننا سنقوم بالتحديث إلى معايير ES6 لاحقًا.
function asyncAddFunction(a, b, callback) { callback(a + b); //This callback is the one passed in to the function call below. } asyncAddFunction(2, 4, function(sum) { //Here we have the sum, 2 + 4 = 6. });
تسمى هذه الوظيفة "دالة الترتيب الأعلى" لأنها تأخذ دالة (رد الاتصال) كوسيطة. بدلاً من ذلك ، قد تأخذ وظيفة رد الاتصال كائن خطأ وكائن استجابة كوسائط ، وتقدمهما عند اكتمال وظيفة غير المتزامن. سنرى هذا لاحقًا مع Express. عندما asyncAddFunction(...)
، ستلاحظ أننا قدمنا وظيفة رد الاتصال لمعامل رد الاتصال من تعريف الطريقة. هذه الوظيفة هي وظيفة مجهولة (ليس لها اسم) وتتم كتابتها باستخدام Expression Syntax . من ناحية أخرى ، فإن تعريف الطريقة هو بيان وظيفة. إنه ليس مجهولاً لأنه يحتوي بالفعل على اسم (وهو "وظيفة غير متزامنة").
قد يلاحظ البعض ارتباكًا لأننا في تعريف الطريقة نقدم اسمًا ، وهو "رد الاتصال". ومع ذلك ، فإن الوظيفة المجهولة التي تم تمريرها كمعامل ثالث إلى asyncAddFunction(...)
لا تعرف الاسم ، ولذا تظل مجهولة المصدر. لا يمكننا أيضًا تنفيذ هذه الوظيفة في وقت لاحق بالاسم ، فسيتعين علينا المرور بوظيفة الاستدعاء غير المتزامن مرة أخرى لإطلاقها.
كمثال على مكالمة متزامنة ، يمكننا استخدام طريقة Node.js readFileSync(...)
. مرة أخرى ، سننتقل إلى ES6 + لاحقًا.
var fs = require('fs'); var data = fs.readFileSync('/example.txt'); // The thread will be blocked here until complete.
إذا كنا نفعل هذا بشكل غير متزامن ، فسنمرر في وظيفة رد الاتصال التي ستطلق عند اكتمال العملية غير المتزامنة.
var fs = require('fs'); var data = fs.readFile('/example.txt', function(err, data) { //Move on, this will fire when ready. if(err) return console.log('Error: ', err); console.log('Data: ', data); // Assume var data is defined above. }); // Keep executing below, don't wait on the data.
إذا لم تكن قد رأيت استخدامًا return
بهذه الطريقة من قبل ، فنحن نقول فقط لإيقاف تنفيذ الوظيفة حتى لا نطبع كائن البيانات إذا تم تعريف كائن الخطأ. كان بإمكاننا أيضًا تغليف بيان السجل في عبارة else
.
مثل asyncAddFunction(...)
، سيكون الرمز الموجود خلف fs.readFile(...)
شيئًا على غرار:
function readFile(path, callback) { // Behind the scenes code to read a file stream. // The data variable is defined up here. callback(undefined, data); //Or, callback(err, undefined); }
اسمح لنا بإلقاء نظرة على تنفيذ أخير لاستدعاء دالة غير متزامنة. سيساعد هذا في ترسيخ فكرة تشغيل وظائف رد الاتصال في وقت لاحق ، وسيساعدنا على فهم تنفيذ برنامج Node.js النموذجي.
setTimeout(function() { // ... }, 1000);
تأخذ طريقة setTimeout(...)
وظيفة رد الاتصال للمعامل الأول الذي سيتم تشغيله بعد حدوث عدد المللي ثانية المحدد باعتباره الوسيطة الثانية.
لنلقِ نظرة على مثال أكثر تعقيدًا:
console.log('Initiated program.'); setTimeout(function() { console.log('3000 ms (3 sec) have passed.'); }, 3000); setTimeout(function() { console.log('0 ms (0 sec) have passed.'); }, 0); setTimeout(function() { console.log('1000 ms (1 sec) has passed.'); }, 1000); console.log('Terminated program');
المخرجات التي نتلقاها هي:
Initiated program. Terminated program. 0 ms (0 sec) have passed. 1000 ms (1 sec) has passed. 3000 ms (3 sec) have passed.
يمكنك أن ترى أن أول بيان سجل يعمل كما هو متوقع. في الحال ، تتم طباعة آخر بيان سجل على الشاشة ، وذلك يحدث قبل 0 ثانية بعد انتهاء setTimeout(...)
. بعد ذلك مباشرة ، يتم تنفيذ الطرق الثانية والثالثة والأولى setTimeout(...)
.
إذا لم يكن Node.js غير محظور ، فسنرى أول بيان سجل ، انتظر 3 ثوان لرؤية التالية ، انظر على الفور الثالثة (0 ثانية setTimeout(...)
، ثم علينا الانتظار مرة أخرى الثانية لرؤية آخر جملتين للسجل. تجعل الطبيعة غير المحظورة للعقدة جميع أجهزة ضبط الوقت تبدأ في العد التنازلي من لحظة تنفيذ البرنامج ، بدلاً من الترتيب الذي تم كتابتها به. قد ترغب في النظر في Node APIs ، Callstack و Event Loop لمزيد من المعلومات حول كيفية عمل Node تحت الغطاء.
من المهم ملاحظة أن مجرد رؤية وظيفة رد الاتصال لا يعني بالضرورة وجود مكالمة غير متزامنة في الكود. لقد أطلقنا على asyncAddFunction(…)
أعلاه "غير متزامن" لأننا نفترض أن العملية تستغرق وقتًا حتى تكتمل - مثل إجراء مكالمة إلى خادم. في الواقع ، عملية إضافة رقمين ليست غير متزامنة ، وبالتالي سيكون هذا في الواقع مثالًا على استخدام وظيفة رد الاتصال بطريقة لا تحجب سلسلة المحادثات في الواقع.
وعود على عمليات الاسترجاعات
يمكن أن تصبح عمليات الاسترجاعات فوضوية بسرعة في JavaScript ، خاصةً عمليات الاسترجاعات المتداخلة المتعددة. نحن معتادون على تمرير رد نداء كوسيطة لوظيفة ما ، لكن الوعود تسمح لنا بإدخال رد نداء أو إرفاقه إلى كائن تم إرجاعه من دالة. سيتيح لنا ذلك التعامل مع مكالمات متعددة غير متزامنة بطريقة أكثر أناقة.
على سبيل المثال ، لنفترض أننا نجري اتصالاً بواجهة برمجة التطبيقات ، وأن وظيفتنا ، التي لا تحمل اسمًا فريدًا " makeAPICall(...)
" ، تأخذ عنوان URL واستدعاء.
سيتم تعريف وظيفتنا ، makeAPICall(...)
، على أنها
function makeAPICall(path, callback) { // Attempt to make API call to path argument. // ... callback(undefined, res); // Or, callback(err, undefined); depending upon the API's response. }
ونسميها بـ:
makeAPICall('/example', function(err1, res1) { if(err1) return console.log('Error: ', err1); // ... });
إذا أردنا إجراء اتصال API آخر باستخدام الاستجابة من الأولى ، فسيتعين علينا إجراء تداخل بين استدعائيهما. افترض أنني بحاجة إلى إدخال خاصية اسم res1
userName
مسار استدعاء API الثاني. سيكون لدينا:
makeAPICall('/example', function(err1, res1) { if(err1) return console.log('Error: ', err1); makeAPICall('/newExample/' + res1.userName, function(err2, res2) { if(err2) return console.log('Error: ', err2); console.log(res2); }); });
ملاحظة : الطريقة ES6 + لإدخال خاصية res1.userName
بدلاً من سلسلة السلسلة ستكون استخدام "سلاسل القوالب". بهذه الطريقة ، بدلاً من تغليف السلسلة بين علامتي اقتباس ( '
، أو "
) ، سنستخدم backticks ( `
). الموجودة أسفل مفتاح Escape على لوحة المفاتيح. وبعد ذلك ، سنستخدم الرمز ${}
لتضمين أي تعبير JS بالداخل الأقواس. في النهاية ، سيكون مسارنا السابق: /newExample/${res.UserName}
، ملفوفًا في backticks.
من الواضح أن هذه الطريقة في تداخل عمليات الاسترجاعات يمكن أن تصبح سريعًا غير أنيقة تمامًا ، ما يسمى "هرم جافا سكريبت من الموت". بالقفز ، إذا كنا نستخدم الوعود بدلاً من عمليات الاسترجاعات ، فيمكننا إعادة صياغة الكود الخاص بنا من المثال الأول على هذا النحو:
makeAPICall('/example').then(function(res) { // Success callback. // ... }, function(err) { // Failure callback. console.log('Error:', err); });
الوسيطة الأولى للدالة then()
هي رد الاتصال للنجاح ، والوسيطة الثانية هي رد الاتصال بالفشل. بدلاً من ذلك ، يمكننا أن نفقد الوسيطة الثانية إلى .then()
، .catch()
بدلاً من ذلك. الوسيطات الخاصة بـ .then ( .then()
اختيارية ، واستدعاء .catch()
سيكون مكافئًا لـ .then(successCallback, null)
.
باستخدام .catch()
، لدينا:
makeAPICall('/example').then(function(res) { // Success callback. // ... }).catch(function(err) { // Failure Callback console.log('Error: ', err); });
يمكننا أيضًا إعادة هيكلة هذا من أجل سهولة القراءة:
makeAPICall('/example') .then(function(res) { // ... }) .catch(function(err) { console.log('Error: ', err); });
من المهم أن نلاحظ أنه لا يمكننا فقط إجراء استدعاء .then()
لأي دالة ونتوقع أن تعمل. يجب على الوظيفة التي ندعوها أن تعيد وعدًا فعليًا ، وهو الوعد الذي سيطلق .then()
عندما تكتمل هذه العملية غير المتزامنة. في هذه الحالة ، makeAPICall(...)
بعمل شيء ما ، حيث يتم إطلاق كتلة then()
أو block catch()
عند الانتهاء.
لجعل makeAPICall(...)
يُعيد وعدًا ، نقوم بتعيين وظيفة إلى متغير ، حيث تكون هذه الوظيفة هي مُنشئ الوعد. يمكن الوفاء بالوعود أو رفضها ، حيث يعني الوفاء بها أن الإجراء المتعلق بالوعد قد اكتمل بنجاح ، ورفضه يعني العكس. بمجرد الوفاء بالوعد أو رفضه ، نقول إنه قد تم تسويته ، وأثناء انتظار تسوية الوعد ، ربما أثناء مكالمة غير متزامنة ، نقول إن الوعد معلق .
يأخذ مُنشئ الوعد دالة رد نداء واحدة كوسيطة ، والتي تتلقى معلمتين - resolve
reject
، والتي سنقوم باستدعاءها في وقت لاحق لإطلاق إما رد النداء بنجاح في .then()
، أو فشل .then()
رد الاتصال ، أو .catch()
، إذا تم توفيره.
فيما يلي مثال لما يبدو عليه هذا:
var examplePromise = new Promise(function(resolve, reject) { // Do whatever we are going to do and then make the appropiate call below: resolve('Happy!'); // — Everything worked. reject('Sad!'); // — We noticed that something went wrong. }):
ثم يمكننا استخدام:
examplePromise.then(/* Both callback functions in here */); // Or, the success callback in .then() and the failure callback in .catch().
لاحظ ، مع ذلك ، أن examplePromise
لا يمكنه تحمل أي حجج. هذا النوع من الضربات يقضي على الهدف ، لذا يمكننا أن نعيد الوعد بدلاً من ذلك.
function makeAPICall(path) { return new Promise(function(resolve, reject) { // Make our async API call here. if (/* All is good */) return resolve(res); //res is the response, would be defined above. else return reject(err); //err is error, would be defined above. }); }
تتألق الوعود حقًا لتحسين هيكل ، وبالتالي أناقة ، رمزنا من خلال مفهوم "تسلسل الوعد". سيسمح لنا هذا بإعادة الوعد الجديد داخل جملة .then()
.then()
، حتى نتمكن من إرفاق.
إعادة هيكلة استدعاء عنوان URL متعدد واجهة برمجة التطبيقات أعلاه مع الوعود ، نحصل على:
makeAPICall('/example').then(function(res) { // First response callback. Fires on success to '/example' call. return makeAPICall(`/newExample/${res.UserName}`); // Returning new call allows for Promise Chaining. }, function(err) { // First failure callback. Fires if there is a failure calling with '/example'. console.log('Error:', err); }).then(function(res) { // Second response callback. Fires on success to returned '/newExample/...' call. console.log(res); }, function(err) { // Second failure callback. Fire if there is a failure calling with '/newExample/...' console.log('Error:', err); });
لاحظ أننا استدعينا أولاً makeAPICall('/example')
. هذا يعيد الوعد ، ولذا فإننا نرفق ملف .then()
. داخل ذلك then()
، نعيد استدعاء جديد لـ makeAPICall(...)
، والذي ، في حد ذاته ، كما رأينا سابقًا ، يُعيد الوعد ، ويسمح لنا بالتسلسل على .then()
جديد بعد الأول.
كما هو مذكور أعلاه ، يمكننا إعادة هيكلة هذا لسهولة القراءة ، وإزالة عمليات رد نداء الفشل من أجل جملة catch()
all. بعد ذلك ، يمكننا اتباع مبدأ الجفاف (لا تكرر نفسك) ، وعلينا فقط تنفيذ معالجة الأخطاء مرة واحدة.
makeAPICall('/example') .then(function(res) { // Like earlier, fires with success and response from '/example'. return makeAPICall(`/newExample/${res.UserName}`); // Returning here lets us chain on a new .then(). }) .then(function(res) { // Like earlier, fires with success and response from '/newExample'. console.log(res); }) .catch(function(err) { // Generic catch all method. Fires if there is an err with either earlier call. console.log('Error: ', err); });
لاحظ أن عمليات رد نداء النجاح والفشل في. ثم .then()
.then()
تطلق فقط من أجل حالة الوعد الفردي الذي يتوافق مع. ومع ذلك ، ستلتقط كتلة catch
أي أخطاء يتم .then()
.
ES6 Const مقابل Let
في جميع الأمثلة لدينا ، كنا نستخدم وظائف ES5 والكلمة الرئيسية القديمة var
. بينما لا تزال ملايين الأسطر من التعليمات البرمجية تعمل اليوم باستخدام طرق ES5 هذه ، فمن المفيد التحديث إلى معايير ES6 + الحالية ، وسنقوم بإعادة تشكيل بعض التعليمات البرمجية أعلاه. لنبدأ بـ const
let
.
قد تكون معتادًا على التصريح عن متغير باستخدام كلمة var
:
var pi = 3.14;
مع معايير ES6 + ، يمكننا تحقيق ذلك أيضًا
let pi = 3.14;
أو
const pi = 3.14;
حيث يعني const
"ثابت" - قيمة لا يمكن إعادة تعيينها لاحقًا. (باستثناء خصائص الكائن - سنغطي ذلك قريبًا. أيضًا ، المتغيرات المعلنة const
ليست ثابتة ، فقط الإشارة إلى المتغير هي.)
في JavaScript القديم ، قم بحظر النطاقات ، مثل تلك الموجودة في if
، while
، {}
. for
، وما إلى ذلك ، لم يؤثر على var
بأي شكل من الأشكال ، وهذا يختلف تمامًا عن اللغات المكتوبة بشكل ثابت مثل Java أو C ++. وهذا يعني أن نطاق var
هو وظيفة التضمين بأكملها - ويمكن أن يكون ذلك عامًا (إذا تم وضعه خارج دالة) ، أو محلي (إذا تم وضعه داخل دالة). لتوضيح ذلك ، انظر المثال التالي:
function myFunction() { var num = 5; console.log(num); // 5 console.log('--'); for(var i = 0; i < 10; i++) { var num = i; console.log(num); //num becomes 0 — 9 } console.log('--'); console.log(num); // 9 console.log(i); // 10 } myFunction();
انتاج:
5 --- 0 1 2 3 ... 7 8 9 --- 9 10
الشيء المهم الذي يجب ملاحظته هنا هو أن تحديد var num
الجديد داخل النطاق for
يؤثر بشكل مباشر على var num
خارج و أعلى for
. هذا لأن نطاق var
دائمًا ما يكون نطاق وظيفة التضمين ، وليس كتلة.
مرة أخرى ، بشكل افتراضي ، var i
من الداخل for()
افتراضيًا على نطاق myFunction
، وبالتالي يمكننا الوصول إلى i
خارج الحلقة والحصول على 10.
فيما يتعلق بتعيين القيم إلى المتغيرات ، let
مع var
، فهذا يعني فقط أن لدينا let
للكتلة ، وبالتالي لن تحدث الحالات الشاذة التي حدثت مع var
أعلاه.
function myFunction() { let num = 5; console.log(num); // 5 for(let i = 0; i < 10; i++) { let num = i; console.log('--'); console.log(num); // num becomes 0 — 9 } console.log('--'); console.log(num); // 5 console.log(i); // undefined, ReferenceError }
بالنظر إلى الكلمة الأساسية const
، يمكنك أن ترى أننا حصلنا على خطأ إذا حاولنا إعادة التعيين إليها:
const c = 299792458; // Fact: The constant "c" is the speed of light in a vacuum in meters per second. c = 10; // TypeError: Assignment to constant variable.
تصبح الأشياء مثيرة للاهتمام عندما نقوم بتعيين متغير const
لكائن:
const myObject = { name: 'Jane Doe' }; // This is illegal: TypeError: Assignment to constant variable. myObject = { name: 'John Doe' }; // This is legal. console.log(myObject.name) -> John Doe myObject.name = 'John Doe';
كما ترى ، فإن الإشارة الموجودة في الذاكرة إلى الكائن المخصص const
ثابت هي فقط غير قابلة للتغيير ، وليست القيمة نفسها.
وظائف السهم ES6
قد تكون معتادًا على إنشاء وظيفة مثل هذا:
function printHelloWorld() { console.log('Hello, World!'); }
مع وظائف السهم ، سيصبح ذلك:
const printHelloWorld = () => { console.log('Hello, World!'); };
افترض أن لدينا دالة بسيطة تُرجع مربع الرقم:
const squareNumber = (x) => { return x * x; } squareNumber(5); // We can call an arrow function like an ES5 functions. Returns 25.
يمكنك أن ترى أنه ، تمامًا كما هو الحال مع دوال ES5 ، يمكننا أخذ الحجج ذات الأقواس ، ويمكننا استخدام عبارات الإرجاع العادية ، ويمكننا استدعاء الوظيفة تمامًا مثل أي وظيفة أخرى.
من المهم ملاحظة أنه في حين أن الأقواس مطلوبة إذا كانت وظيفتنا لا تأخذ أي وسيطات (مثل printHelloWorld()
أعلاه) ، يمكننا إسقاط الأقواس إذا كانت تأخذ واحدًا فقط ، لذلك يمكن إعادة كتابة تعريف طريقة squareNumber()
السابق على النحو التالي:
const squareNumber = x => { // Notice we have dropped the parentheses for we only take in one argument. return x * x; }
سواء اخترت تغليف وسيطة واحدة بين قوسين أم لا ، فهي مسألة ذوق شخصي ، ومن المحتمل أن ترى المطورين يستخدمون كلتا الطريقتين.
أخيرًا ، إذا أردنا فقط إرجاع تعبير واحد ضمنيًا ، كما هو الحال مع squareNumber(...)
أعلاه ، فيمكننا وضع تعليمة الإرجاع في خط مع توقيع الطريقة:
const squareNumber = x => x * x;
إنه،
const test = (a, b, c) => expression
بالضبط مثل
const test = (a, b, c) => { return expression }
لاحظ أنه عند استخدام الاختصار أعلاه لإرجاع كائن ضمنيًا ، تصبح الأشياء غامضة. ما الذي يمنع JavaScript من الاعتقاد بأن الأقواس التي يُطلب منا من خلالها تغليف كائننا ليست هي جسم وظيفتنا؟ للتغلب على هذا ، نقوم بلف أقواس الكائن بين قوسين. يتيح هذا لـ JavaScript معرفة أننا بالفعل نعيد كائنًا ، ولا نقوم فقط بتعريف الجسم.
const test = () => ({ pi: 3.14 }); // Spaces between brackets are a formality to make the code look cleaner.
للمساعدة في ترسيخ مفهوم وظائف ES6 ، سنقوم بإعادة تشكيل بعض التعليمات البرمجية السابقة مما يسمح لنا بمقارنة الاختلافات بين كلا الترميزين.
يمكن asyncAddFunction(...)
الواردة أعلاه من:
function asyncAddFunction(a, b, callback){ callback(a + b); }
ل:
const aysncAddFunction = (a, b, callback) => { callback(a + b); };
أو حتى:
const aysncAddFunction = (a, b, callback) => callback(a + b); // This will return callback(a + b).
عند استدعاء الوظيفة ، يمكننا تمرير وظيفة سهم لرد الاتصال:
asyncAddFunction(10, 12, sum => { // No parentheses because we only take one argument. console.log(sum); }
من الواضح أن نرى كيف تعمل هذه الطريقة على تحسين قابلية قراءة الكود. لتظهر لك حالة واحدة فقط ، يمكننا أخذ المثال القديم المبني على وعد ES5 أعلاه ، وإعادة بنائه لاستخدام وظائف الأسهم.
makeAPICall('/example') .then(res => makeAPICall(`/newExample/${res.UserName}`)) .then(res => console.log(res)) .catch(err => console.log('Error: ', err));
الآن ، هناك بعض المحاذير المتعلقة بوظائف الأسهم. لأحد ، لا تربط this
الكلمة الأساسية. افترض أن لدي الكائن التالي:
const Person = { name: 'John Doe', greeting: () => { console.log(`Hi. My name is ${this.name}.`); } }
قد تتوقع أن مكالمة إلى Person.greeting()
سترد "مرحبًا. اسمي جون دو. " بدلاً من ذلك ، نحصل على: "مرحبًا. اسمي غير محدد ". وذلك لأن وظائف السهم لا تحتوي على this
، وبالتالي فإن محاولة استخدام this
داخل وظيفة السهم يتم تعيينها افتراضيًا على نطاق التضمين this
، والنطاق التضمين لكائن Person
هو window
، في المستعرض ، أو module.exports
العقدة.
لإثبات ذلك ، إذا استخدمنا نفس الكائن مرة أخرى ، لكننا عيّننا خاصية name
this
إلى شيء مثل "Jane Doe" ، فإن this.name
في دالة السهم ترجع "Jane Doe" ، لأن this
العام موجود داخل نطاق التضمين ، أو هو أصل كائن Person
.
this.name = 'Jane Doe'; const Person = { name: 'John Doe', greeting: () => { console.log(`Hi. My name is ${this.name}.`); } } Person.greeting(); // Hi. My name is Jane Doe
يُعرف هذا باسم "النطاق المعجمي" ، ويمكننا الالتفاف حوله باستخدام ما يسمى بـ "التركيب المختصر" ، حيث نفقد القولون والسهم لإعادة تشكيل كائننا على هذا النحو:
const Person = { name: 'John Doe', greeting() { console.log(`Hi. My name is ${this.name}.`); } } Person.greeting() //Hi. My name is John Doe.
فئات ES6
بينما لم تدعم JavaScript الفئات مطلقًا ، يمكنك دائمًا محاكاتها باستخدام كائنات مثل المذكورة أعلاه. يوفر EcmaScript 6 دعمًا للفئات التي تستخدم class
والكلمات الرئيسية new
:
class Person { constructor(name) { this.name = name; } greeting() { console.log(`Hi. My name is ${this.name}.`); } } const person = new Person('John'); person.greeting(); // Hi. My name is John.
يتم استدعاء وظيفة المُنشئ تلقائيًا عند استخدام الكلمة الأساسية new
، والتي يمكننا من خلالها تمرير الوسيطات لإعداد الكائن في البداية. يجب أن يكون هذا مألوفًا لأي قارئ لديه خبرة في المزيد من لغات البرمجة الموجهة للكائنات المكتوبة بشكل ثابت مثل Java و C ++ و C #.
بدون الخوض في الكثير من التفاصيل حول مفاهيم OOP ، هناك نموذج آخر من هذا القبيل هو "الميراث" ، وهو السماح لفئة ما بأن ترث من أخرى. فئة تسمى Car
، على سبيل المثال ، ستكون عامة جدًا - تحتوي على طرق مثل "Stop" و "start" وما إلى ذلك ، حسب احتياج جميع السيارات. قد ترث مجموعة فرعية من الفئة تسمى SportsCar
العمليات الأساسية من Car
وتتجاوز أي شيء يحتاجه مخصص. يمكننا الإشارة إلى هذه الفئة على النحو التالي:
class Car { constructor(licensePlateNumber) { this.licensePlateNumber = licensePlateNumber; } start() {} stop() {} getLicensePlate() { return this.licensePlateNumber; } // … } class SportsCar extends Car { constructor(engineRevCount, licensePlateNumber) { super(licensePlateNumber); // Pass licensePlateNumber up to the parent class. this.engineRevCount = engineRevCount; } start() { super.start(); } stop() { super.stop(); } getLicensePlate() { return super.getLicensePlate(); } getEngineRevCount() { return this.engineRevCount; } }
يمكنك أن ترى بوضوح أن الكلمة المفتاحية super
تسمح لنا بالوصول إلى الخصائص والطرق من الوالد ، أو الطبقة الفائقة.
أحداث جافا سكريبت
الحدث هو إجراء يحدث ولديك القدرة على الاستجابة له. افترض أنك تقوم بإنشاء نموذج تسجيل دخول لتطبيقك. عندما يضغط المستخدم على زر "إرسال" ، يمكنك الرد على هذا الحدث عبر "معالج الحدث" في التعليمات البرمجية - وهي عادةً وظيفة. عندما يتم تعريف هذه الوظيفة على أنها معالج الأحداث ، فإننا نقول إننا "نسجل معالج الحدث". من المحتمل أن يتحقق معالج الحدث الخاص بنقرة زر الإرسال من تنسيق الإدخال المقدم من قبل المستخدم ، وتعقيمه لمنع مثل هذه الهجمات مثل SQL Injections أو Cross Site Scripting (يرجى العلم أنه لا يمكن اعتبار أي كود من جانب العميل على الإطلاق آمن. قم دائمًا بتعقيم البيانات الموجودة على الخادم - لا تثق أبدًا في أي شيء من المتصفح) ، ثم تحقق لمعرفة ما إذا كانت تركيبة اسم المستخدم وكلمة المرور هذه تخرج داخل قاعدة بيانات لمصادقة المستخدم وتقديم رمز مميز له.
نظرًا لأن هذه مقالة حول Node ، فسنركز على نموذج حدث Node.
يمكننا استخدام وحدة events
من Node لإرسال أحداث معينة والتفاعل معها. أي كائن يرسل حدثًا هو مثيل لفئة EventEmitter
.
يمكننا إرسال حدث عن طريق استدعاء طريقة emit()
ونستمع لهذا الحدث عبر طريقة on()
، وكلاهما يتم عرضه من خلال فئة EventEmitter
.
const EventEmitter = require('events'); const myEmitter = new EventEmitter();
مع myEmitter
الآن مثيل لفئة EventEmitter
، يمكننا الوصول إلى emit emit()
وعلى on()
:
const EventEmitter = require('events'); const myEmitter = new EventEmitter(); myEmitter.on('someEvent', () => { console.log('The "someEvent" event was fired (emitted)'); }); myEmitter.emit('someEvent'); // This will call the callback function above.
المعلمة الثانية لـ myEmitter.on()
هي وظيفة رد الاتصال التي سيتم إطلاقها عند إصدار الحدث - هذا هو معالج الحدث. المعلمة الأولى هي اسم الحدث ، والذي يمكن أن يكون أي شيء نحبه ، على الرغم من أن اصطلاح تسمية camelCase موصى به.
بالإضافة إلى ذلك ، يمكن لمعالج الحدث أن يأخذ أي عدد من الوسائط ، والتي يتم تمريرها لأسفل عند إرسال الحدث:
const EventEmitter = require('events'); const myEmitter = new EventEmitter(); myEmitter.on('someEvent', (data) => { console.log(`The "someEvent" event was fired (emitted) with data: ${data}`); }); myEmitter.emit('someEvent', 'This is the data payload');
باستخدام الوراثة ، يمكننا كشف التابعين emit emit()
و on()
من EventEmitter إلى أي فئة. يتم ذلك عن طريق إنشاء فئة Node.js ، واستخدام الكلمة الأساسية EventEmitter
extends
const EventEmitter = require('events'); class MyEmitter extends EventEmitter { // This is my class. I can emit events from a MyEmitter object. }
لنفترض أننا نبني برنامجًا للإعلام عن اصطدام السيارة يتلقى البيانات من الجيروسكوبات ومقاييس التسارع ومقاييس الضغط على هيكل السيارة. عندما تصطدم مركبة بجسم ما ، ستكتشف تلك المستشعرات الخارجية الحادث ، وتنفذ وظيفة collide(...)
وتمرر إليها بيانات المستشعر المجمعة ككائن جافا سكريبت لطيف. ستصدر هذه الوظيفة حدث collision
، لإعلام البائع بالتعطل.
const EventEmitter = require('events'); class Vehicle extends EventEmitter { collide(collisionStatistics) { this.emit('collision', collisionStatistics) } } const myVehicle = new Vehicle(); myVehicle.on('collision', collisionStatistics => { console.log('WARNING! Vehicle Impact Detected: ', collisionStatistics); notifyVendor(collisionStatistics); }); myVehicle.collide({ ... });
هذا مثال معقد لأنه يمكننا فقط وضع الكود داخل معالج الأحداث داخل وظيفة الاصطدام للفئة ، لكنه يوضح كيفية عمل Node Event Model على الرغم من ذلك. لاحظ أن بعض البرامج التعليمية ستُظهر طريقة util.inherits()
للسماح لكائن بإصدار أحداث. تم إهمال ذلك لصالح فئات ES6 extends
.
مدير حزمة العقدة
عند البرمجة باستخدام Node و JavaScript ، سيكون من الشائع جدًا معرفة npm
. Npm هو مدير حزم يقوم بذلك بالضبط - يسمح بتنزيل حزم الجهات الخارجية التي تحل المشكلات الشائعة في JavaScript. توجد حلول أخرى ، مثل Yarn و Npx و Grunt و Bower أيضًا ، ولكن في هذا القسم ، سنركز فقط على npm
وكيف يمكنك تثبيت التبعيات لتطبيقك من خلال واجهة سطر أوامر بسيطة (CLI) تستخدمها.
لنبدأ ببساطة ، مع npm
فقط. قم بزيارة الصفحة الرئيسية لـ NpmJS لعرض جميع الحزم المتاحة من NPM. عندما تبدأ مشروعًا جديدًا يعتمد على حزم NPM ، سيتعين عليك تشغيل npm init
من خلال الجهاز الطرفي في الدليل الجذر لمشروعك. سيُطلب منك سلسلة من الأسئلة التي سيتم استخدامها لإنشاء ملف package.json
. يخزن هذا الملف جميع التبعيات الخاصة بك - الوحدات النمطية التي يعتمد عليها تطبيقك للعمل ، والبرامج النصية - أوامر المحطة الطرفية المحددة مسبقًا لإجراء الاختبارات ، وبناء المشروع ، وبدء خادم التطوير ، وما إلى ذلك ، والمزيد.
لتثبيت حزمة ، ما عليك سوى تشغيل npm install [package-name] --save
. ستضمن علامة save
تسجيل الحزمة وإصدارها في ملف package.json
. منذ الإصدار 5 من npm
، يتم حفظ التبعيات افتراضيًا ، لذلك قد يتم حذف --save
. ستلاحظ أيضًا مجلدًا جديدًا node_modules
، يحتوي على رمز تلك الحزمة التي قمت بتثبيتها للتو. يمكن أيضًا اختصارها إلى npm i [package-name]
فقط. كملاحظة مفيدة ، لا ينبغي أبدًا تضمين مجلد node_modules
في مستودع GitHub نظرًا لحجمه. عندما تقوم باستنساخ repo من GitHub (أو أي نظام إدارة إصدارات آخر) ، تأكد من تشغيل الأمر npm install
للخروج وجلب جميع الحزم المحددة في ملف package.json
، وإنشاء دليل node_modules
تلقائيًا. يمكنك أيضًا تثبيت حزمة بإصدار محدد: npm i [package-name]@1.10.1 --save
، على سبيل المثال.
تشبه إزالة حزمة تثبيت حزمة واحدة: npm remove [package-name]
.
يمكنك أيضًا تثبيت حزمة على مستوى العالم. ستكون هذه الحزمة متاحة في جميع المشاريع ، وليس فقط المشروع الذي تعمل عليه. يمكنك القيام بذلك باستخدام العلامة -g
بعد npm i [package-name]
. يستخدم هذا بشكل شائع مع CLIs ، مثل Google Firebase و Heroku. على الرغم من السهولة التي توفرها هذه الطريقة ، إلا أنه يُعتبر عمومًا ممارسة سيئة لتثبيت الحزم عالميًا ، حيث لا يتم حفظها في ملف package.json
، وإذا حاول مطور آخر استخدام مشروعك ، فلن يحصلوا على جميع التبعيات المطلوبة من npm install
.
واجهات برمجة التطبيقات و JSON
تعد واجهات برمجة التطبيقات (API) نموذجًا شائعًا جدًا في البرمجة ، وحتى إذا كنت بدأت للتو في حياتك المهنية كمطور ، فمن المحتمل أن تظهر واجهات برمجة التطبيقات واستخدامها ، خاصة في تطوير الويب والهاتف المحمول ، في كثير من الأحيان.
واجهة برمجة التطبيقات (API) هي واجهة برمجة تطبيقات ، وهي في الأساس طريقة يمكن من خلالها أن يتواصل نظامان منفصلان مع بعضهما البعض. بمصطلحات تقنية أكثر ، تسمح واجهة برمجة التطبيقات لنظام أو برنامج كمبيوتر (خادم عادةً) بتلقي الطلبات وإرسال الاستجابات المناسبة (إلى العميل ، المعروف أيضًا باسم المضيف).
لنفترض أنك تبني تطبيقًا للطقس. أنت بحاجة إلى طريقة لترميز عنوان المستخدم جغرافيًا في خطوط الطول والعرض ، ثم طريقة للوصول إلى الطقس الحالي أو المتوقع في هذا الموقع المحدد.
As a developer, you want to focus on building your app and monetizing it, not putting the infrastructure in place to geocode addresses or placing weather stations in every city.
Luckily for you, companies like Google and OpenWeatherMap have already put that infrastructure in place, you just need a way to talk to it — that is where the API comes in. While, as of now, we have developed a very abstract and ambiguous definition of the API, bear with me. We'll be getting to tangible examples soon.
Now, it costs money for companies to develop, maintain, and secure that aforementioned infrastructure, and so it is common for corporations to sell you access to their API. This is done with that is known as an API key, a unique alphanumeric identifier associating you, the developer, with the API. Every time you ask the API to send you data, you pass along your API key. The server can then authenticate you and keep track of how many API calls you are making, and you will be charged appropriately. The API key also permits Rate-Limiting or API Call Throttling (a method of throttling the number of API calls in a certain timeframe as to not overwhelm the server, preventing DOS attacks — Denial of Service). Most companies, however, will provide a free quota, giving you, as an example, 25,000 free API calls a day before charging you.
Up to this point, we have established that an API is a method by which two computer programs can communicate with each other. If a server is storing data, such as a website, and your browser makes a request to download the code for that site, that was the API in action.
Let us look at a more tangible example, and then we'll look at a more real-world, technical one. Suppose you are eating out at a restaurant for dinner. You are equivalent to the client, sitting at the table, and the chef in the back is equivalent to the server.
Since you will never directly talk to the chef, there is no way for him/her to receive your request (for what order you would like to make) or for him/her to provide you with your meal once you order it. We need someone in the middle. In this case, it's the waiter, analogous to the API. The API provides a medium with which you (the client) may talk to the server (the chef), as well as a set of rules for how that communication should be made (the menu — one meal is allowed two sides, etc.)
Now, how do you actually talk to the API (the waiter)? You might speak English, but the chef might speak Spanish. Is the waiter expected to know both languages to translate? What if a third person comes in who only speaks Mandarin? What then? Well, all clients and servers have to agree to speak a common language, and in computer programming, that language is JSON, pronounced JAY-sun, and it stands for JavaScript Object Notation.
At this point, we don't quite know what JSON looks like. It's not a computer programming language, it's just, well, a language, like English or Spanish, that everyone (everyone being computers) understands on a guaranteed basis. It's guaranteed because it's a standard, notably RFC 8259 , the JavaScript Object Notation (JSON) Data Interchange Format by the Internet Engineering Task Force (IETF).
Even without formal knowledge of what JSON actually is and what it looks like (we'll see in an upcoming article in this series), we can go ahead introduce a technical example operating on the Internet today that employs APIs and JSON. APIs and JSON are not just something you can choose to use, it's not equivalent to one out of a thousand JavaScript frameworks you can pick to do the same thing. It is THE standard for data exchange on the web.
Suppose you are building a travel website that compares prices for aircraft, rental car, and hotel ticket prices. Let us walk through, step-by-step, on a high level, how we would build such an application. Of course, we need our User Interface, the front-end, but that is out of scope for this article.
We want to provide our users with the lowest price booking method. Well, that means we need to somehow attain all possible booking prices, and then compare all of the elements in that set (perhaps we store them in an array) to find the smallest element (known as the infimum in mathematics.)
How will we get this data? Well, suppose all of the booking sites have a database full of prices. Those sites will provide an API, which exposes the data in those databases for use by you. You will call each API for each site to attain all possible booking prices, store them in your own array, find the lowest or minimum element of that array, and then provide the price and booking link to your user. We'll ask the API to query its database for the price in JSON, and it will respond with said price in JSON to us. We can then use, or parse, that accordingly. We have to parse it because APIs will return JSON as a string, not the actual JavaScript data type of JSON. This might not make sense now, and that's okay. We'll be covering it more in a future article.
Also, note that just because something is called an API does not necessarily mean it operates on the web and sends and receives JSON. The Java API, for example, is just the list of classes, packages, and interfaces that are part of the Java Development Kit (JDK), providing programming functionality to the programmer.
تمام. We know we can talk to a program running on a server by way of an Application Programming Interface, and we know that the common language with which we do this is known as JSON. But in the web development and networking world, everything has a protocol. What do we actually do to make an API call, and what does that look like code-wise? That's where HTTP Requests enter the picture, the HyperText Transfer Protocol, defining how messages are formatted and transmitted across the Internet. Once we have an understanding of HTTP (and HTTP verbs, you'll see that in the next section), we can look into actual JavaScript frameworks and methods (like fetch()
) offered by the JavaScript API (similar to the Java API), that actually allow us to make API calls.
HTTP And HTTP Requests
HTTP is the HyperText Transfer Protocol. It is the underlying protocol that determines how messages are formatted as they are transmitted and received across the web. Let's think about what happens when, for example, you attempt to load the home page of Smashing Magazine in your web browser.
You type the website URL (Uniform Resource Locator) in the URL bar, where the DNS server (Domain Name Server, out of scope for this article) resolves the URL into the appropriate IP Address. The browser makes a request, called a GET Request, to the Web Server to, well, GET the underlying HTML behind the site. The Web Server will respond with a message such as “OK”, and then will go ahead and send the HTML down to the browser where it will be parsed and rendered accordingly.
There are a few things to note here. First, the GET Request, and then the “OK” response. Suppose you have a specific database, and you want to write an API to expose that database to your users. Suppose the database contains books the user wants to read (as it will in a future article in this series). Then there are four fundamental operations your user may want to perform on this database, that is, Create a record, Read a record, Update a record, or Delete a record, known collectively as CRUD operations.
Let's look at the Read operation for a moment. Without incorrectly assimilating or conflating the notion of a web server and a database, that Read operation is very similar to your web browser attempting to get the site from the server, just as to read a record is to get the record from the database.
يُعرف هذا باسم طلب HTTP. أنت تقدم طلبًا إلى خادم ما في مكان ما للحصول على بعض البيانات ، وعلى هذا النحو ، يُطلق على الطلب اسم "GET" بشكل مناسب ، حيث تُعد الكتابة بالأحرف الكبيرة طريقة قياسية للإشارة إلى مثل هذه الطلبات.
ماذا عن إنشاء جزء من الخام؟ حسنًا ، عند الحديث عن طلبات HTTP ، يُعرف ذلك باسم طلب POST. تمامًا كما يمكنك نشر رسالة على منصة وسائط اجتماعية ، يمكنك أيضًا نشر سجل جديد في قاعدة بيانات.
يسمح لنا تحديث CRUD باستخدام إما PUT أو PATCH Request لتحديث أحد الموارد. سيقوم PUT الخاص بـ HTTP إما بإنشاء سجل جديد أو تحديث / استبدال السجل القديم.
لنلقِ نظرة أكثر تفصيلاً على هذا ، ثم ننتقل إلى التصحيح.
تعمل واجهة برمجة التطبيقات بشكل عام عن طريق تقديم طلبات HTTP إلى مسارات محددة في عنوان URL. لنفترض أننا نصنع واجهة برمجة تطبيقات للتحدث إلى قاعدة بيانات تحتوي على قائمة كتب المستخدم. ثم قد نتمكن من عرض هذه الكتب على URL .../books
. طلبات POST إلى .../books
جديدًا بأي خصائص تحددها (معرف التفكير ، العنوان ، رقم ISBN ، المؤلف ، بيانات النشر ، إلخ) في مسار .../books
. لا يهم ما هي بنية البيانات الأساسية التي تخزن جميع الكتب في .../books
في الوقت الحالي. نحن نهتم فقط أن تعرض واجهة برمجة التطبيقات (API) نقطة النهاية (التي يتم الوصول إليها من خلال المسار) لمعالجة البيانات. كانت الجملة السابقة أساسية: ينشئ طلب POST كتابًا جديدًا في ...books/
route. الفرق بين PUT و POST ، إذن ، هو أن PUT ستنشئ كتابًا جديدًا (كما هو الحال مع POST) إذا لم يكن هذا الكتاب موجودًا ، أو أنه سيحل محل الكتاب الحالي إذا كان الكتاب موجودًا بالفعل ضمن بنية البيانات المذكورة أعلاه.
لنفترض أن كل كتاب يحتوي على الخصائص التالية: المعرف والعنوان ورقم ISBN والمؤلف و hasRead (قيمة منطقية).
ثم لإضافة كتاب جديد ، كما رأينا سابقًا ، سنقوم بتقديم طلب POST إلى .../books
. إذا أردنا تحديث كتاب أو استبداله بالكامل ، فسنقدم طلب PUT إلى .../books/id
حيث id
هو معرف الكتاب الذي نريد استبداله.
بينما يستبدل PUT كتابًا موجودًا تمامًا ، يقوم PATCH بتحديث شيء له علاقة بكتاب معين ، وربما يتم تعديل خاصية hasRead
المنطقية التي حددناها أعلاه - لذلك سنقوم بتقديم طلب PATCH إلى …/books/id
إرسال البيانات الجديدة.
قد يكون من الصعب رؤية معنى هذا في الوقت الحالي ، لأننا حتى الآن أنشأنا كل شيء من الناحية النظرية ولكننا لم نر أي كود ملموس يقوم بالفعل بطلب HTTP. ومع ذلك ، سنصل إلى ذلك قريبًا ، مع تغطية GET في هذه المقالة ، وإعلان الباقي في مقال مستقبلي.
هناك عملية CRUD أساسية أخيرة وتسمى حذف. كما تتوقع ، فإن اسم طلب HTTP هذا هو "DELETE" ، وهو يعمل بشكل مشابه تمامًا لـ PATCH ، مما يتطلب توفير معرف الكتاب في المسار.
لقد تعلمنا حتى الآن ، إذن ، أن المسارات عبارة عن عناوين URL محددة تقوم بإجراء طلب HTTP إليها ، وأن نقاط النهاية هي وظائف توفرها واجهة برمجة التطبيقات ، وتقوم بشيء ما للبيانات التي تعرضها. وهذا يعني أن نقطة النهاية هي وظيفة لغة برمجة تقع على الطرف الآخر من المسار ، وتقوم بتنفيذ أي طلب HTTP تحدده. لقد تعلمنا أيضًا أن هناك مصطلحات مثل POST و GET و PUT و PATCH و DELETE والمزيد (المعروفة باسم أفعال HTTP) التي تحدد في الواقع الطلبات التي تقدمها لواجهة برمجة التطبيقات. مثل JSON ، فإن طرق طلب HTTP هذه هي معايير الإنترنت على النحو المحدد من قبل فريق عمل هندسة الإنترنت (IETF) ، وعلى الأخص RFC 7231 ، القسم الرابع: طرق الطلب ، و RFC 5789 ، القسم الثاني: طريقة التصحيح ، حيث RFC هو اختصار لـ طلب للحصول على تعليقات.
لذلك ، قد نقوم بتقديم طلب GET إلى عنوان URL .../books/id
حيث يُعرف المعرف الذي تم تمريره بالمعامل. يمكننا تقديم طلب POST أو PUT أو PATCH إلى .../books
لإنشاء مورد أو .../books/id
لتعديل / استبدال / تحديث مورد. ويمكننا أيضًا تقديم طلب DELETE إلى .../books/id
لحذف كتاب معين.
يمكن العثور على قائمة كاملة بأساليب طلب HTTP هنا.
من المهم أيضًا ملاحظة أنه بعد إجراء طلب HTTP ، سنتلقى ردًا. يتم تحديد الاستجابة المحددة من خلال كيفية إنشاء واجهة برمجة التطبيقات ، ولكن يجب أن تتلقى دائمًا رمز الحالة. في وقت سابق ، قلنا أنه عندما يطلب متصفح الويب الخاص بك HTML من خادم الويب ، فسوف يستجيب بـ "OK". يُعرف ذلك برمز حالة HTTP ، وبشكل أكثر تحديدًا ، HTTP 200 OK. يحدد رمز الحالة فقط كيفية اكتمال العملية أو الإجراء المحدد في نقطة النهاية (تذكر أن هذه هي وظيفتنا التي تقوم بكل العمل). يتم إرسال أكواد حالة HTTP مرة أخرى بواسطة الخادم ، ومن المحتمل أن يكون هناك العديد ممن تعرفهم ، مثل 404 لم يتم العثور على (تعذر العثور على المورد أو الملف ، سيكون هذا مثل تقديم طلب GET إلى .../books/id
حيث لا يوجد مثل هذا المعرف.)
يمكن العثور على قائمة كاملة من أكواد حالة HTTP هنا.
MongoDB
MongoDB هي قاعدة بيانات NoSQL غير علائقية تشبه قاعدة بيانات Firebase Real-time Database. سوف تتحدث إلى قاعدة البيانات عبر حزمة Node مثل MongoDB Native Driver أو Mongoose.
في MongoDB ، يتم تخزين البيانات في JSON ، والتي تختلف تمامًا عن قواعد البيانات العلائقية مثل MySQL أو PostgreSQL أو SQLite. كلاهما يسمى قواعد البيانات ، مع جداول SQL تسمى المجموعات ، وصفوف جدول SQL تسمى المستندات ، وأعمدة جدول SQL تسمى الحقول.
سنستخدم قاعدة بيانات MongoDB في مقال قادم في هذه السلسلة عندما ننشئ أول واجهة برمجة تطبيقات خاصة بنا على Bookshelf. يمكن إجراء عمليات CRUD الأساسية المذكورة أعلاه على قاعدة بيانات MongoDB.
يوصى بقراءة MongoDB Docs لتتعلم كيفية إنشاء قاعدة بيانات مباشرة على Atlas Cluster وجعل عمليات CRUD لها باستخدام برنامج MongoDB Native Driver. في المقالة التالية من هذه السلسلة ، سنتعلم كيفية إعداد قاعدة بيانات محلية وقاعدة بيانات إنتاج سحابي.
بناء تطبيق عقدة سطر الأوامر
عند إنشاء تطبيق ، سترى العديد من المؤلفين يقومون بتفريغ قاعدة التعليمات البرمجية بالكامل في بداية المقالة ، ثم يحاولون شرح كل سطر بعد ذلك. في هذا النص ، سأتخذ نهجًا مختلفًا. سأشرح الكود الخاص بي سطراً بسطر ، وأقوم ببناء التطبيق كما نبدأ. لن أقلق بشأن النمطية أو الأداء ، ولن أقوم بتقسيم قاعدة التعليمات البرمجية إلى ملفات منفصلة ، ولن أتبع مبدأ DRY أو أحاول جعل الكود قابلاً لإعادة الاستخدام. عند التعلم فقط ، من المفيد جعل الأمور بسيطة قدر الإمكان ، وهذا هو النهج الذي سأتبعه هنا.
دعونا نكون واضحين بشأن ما نبنيه. لن نهتم بإدخال المستخدم ، لذا لن نستخدم حزم مثل Yargs. كما أننا لن نبني API الخاصة بنا. سيأتي ذلك في مقال لاحق في هذه السلسلة عندما نستخدم Express Web Application Framework. أتخذ هذا النهج لعدم الخلط بين Node.js وقوة Express و APIs لأن معظم البرامج التعليمية تفعل ذلك. بدلاً من ذلك ، سأقدم طريقة واحدة (من بين العديد) لاستدعاء واستقبال البيانات من واجهة برمجة تطبيقات خارجية تستخدم مكتبة جافا سكريبت تابعة لجهة خارجية. واجهة برمجة التطبيقات التي سنتصل بها هي واجهة برمجة تطبيقات Weather ، والتي سنصل إليها من Node ونقوم بتفريغ مخرجاتها إلى المحطة ، ربما مع بعض التنسيق ، المعروف باسم "الطباعة الجميلة". سأغطي العملية بأكملها ، بما في ذلك كيفية إعداد واجهة برمجة التطبيقات (API) والوصول إلى مفتاح واجهة برمجة التطبيقات (API) ، والتي توفر خطواتها النتائج الصحيحة اعتبارًا من يناير 2019.
سنستخدم OpenWeatherMap API لهذا المشروع ، لذلك للبدء ، انتقل إلى صفحة تسجيل OpenWeatherMap وأنشئ حسابًا باستخدام النموذج. بمجرد تسجيل الدخول ، ابحث عن عنصر قائمة API Keys في صفحة لوحة القيادة (الموجودة هنا). إذا كنت قد أنشأت حسابًا للتو ، فسيتعين عليك اختيار اسم لمفتاح API والضغط على "إنشاء". قد يستغرق الأمر ساعتين على الأقل حتى يعمل مفتاح واجهة برمجة التطبيقات الجديد الخاص بك ويرتبط بحسابك.
قبل أن نبدأ في إنشاء التطبيق ، سنقوم بزيارة وثائق API لمعرفة كيفية تنسيق مفتاح API الخاص بنا. في هذا المشروع ، سنحدد رمزًا بريديًا ورمز دولة للحصول على معلومات الطقس في هذا الموقع.
من المستندات ، يمكننا أن نرى أن الطريقة التي نقوم بها بذلك هي توفير عنوان URL التالي:
api.openweathermap.org/data/2.5/weather?zip={zip code},{country code}
حيث يمكننا إدخال البيانات:
api.openweathermap.org/data/2.5/weather?zip=94040,us
الآن ، قبل أن نتمكن فعليًا من الحصول على البيانات ذات الصلة من واجهة برمجة التطبيقات هذه ، سنحتاج إلى توفير مفتاح واجهة برمجة التطبيقات الجديد كمعامل استعلام:
api.openweathermap.org/data/2.5/weather?zip=94040,us&appid={YOUR_API_KEY}
في الوقت الحالي ، انسخ عنوان URL هذا في علامة تبويب جديدة في متصفح الويب لديك ، واستبدل العنصر النائب {YOUR_API_KEY}
واجهة برمجة التطبيقات الذي حصلت عليه سابقًا عند التسجيل للحصول على حساب.
النص الذي يمكنك رؤيته هو في الواقع JSON - لغة الويب المتفق عليها كما تمت مناقشتها سابقًا.
لفحص ذلك بشكل أكبر ، اضغط على Ctrl + Shift + I في Google Chrome لفتح أدوات Chrome Developer ، ثم انتقل إلى علامة التبويب Network. في الوقت الحاضر ، لا ينبغي أن تكون هناك بيانات هنا.
لمراقبة بيانات الشبكة فعليًا ، أعد تحميل الصفحة ، وشاهد علامة التبويب مليئة بالمعلومات المفيدة. انقر فوق الارتباط الأول كما هو موضح في الصورة أدناه.
بمجرد النقر فوق هذا الارتباط ، يمكننا بالفعل عرض معلومات محددة لـ HTTP ، مثل الرؤوس. يتم إرسال الرؤوس في الاستجابة من واجهة برمجة التطبيقات (يمكنك أيضًا ، في بعض الحالات ، إرسال الرؤوس الخاصة بك إلى واجهة برمجة التطبيقات ، أو يمكنك حتى إنشاء رؤوس مخصصة (غالبًا ما تكون مسبوقة بـ x-
) لإرسالها مرة أخرى عند إنشاء واجهة برمجة التطبيقات الخاصة بك ) ، وتحتوي فقط على معلومات إضافية قد يحتاجها العميل أو الخادم.
في هذه الحالة ، يمكنك أن ترى أننا قدمنا طلب HTTP GET لواجهة برمجة التطبيقات ، وقد استجاب بـ HTTP Status 200 OK. يمكنك أيضًا رؤية أن البيانات المرسلة كانت بتنسيق JSON ، كما هو مدرج ضمن قسم "عناوين الاستجابة".
إذا قمت بالضغط على علامة تبويب المعاينة ، يمكنك بالفعل عرض JSON ككائن JavaScript. الإصدار النصي الذي يمكنك رؤيته في متصفحك عبارة عن سلسلة ، لأن JSON يتم إرسالها واستلامها دائمًا عبر الويب كسلسلة. لهذا السبب يتعين علينا تحليل JSON في الكود الخاص بنا ، للحصول عليه بتنسيق أكثر قابلية للقراءة - في هذه الحالة (وفي كل حالة تقريبًا) - كائن JavaScript.
يمكنك أيضًا استخدام امتداد Google Chrome "عرض JSON" للقيام بذلك تلقائيًا.
لبدء إنشاء تطبيقنا ، سأفتح Terminal وأقوم بإنشاء دليل جذر جديد ثم cd
فيه. بمجرد الدخول ، سأقوم بإنشاء ملف app.js
جديد ، وتشغيل npm init
لإنشاء ملف package.json
بالإعدادات الافتراضية ، ثم افتح Visual Studio Code.
mkdir command-line-weather-app && cd command-line-weather-app touch app.js npm init code .
بعد ذلك ، سأقوم بتنزيل Axios ، والتحقق من إضافته إلى ملف package.json
الخاص بي ، ولاحظ أنه تم إنشاء مجلد node_modules
بنجاح.
في المتصفح ، يمكنك أن ترى أننا قدمنا طلب GET يدويًا عن طريق كتابة عنوان URL المناسب يدويًا في شريط URL. أكسيوس هو ما سيسمح لي بالقيام بذلك داخل العقدة.
بدءًا من الآن ، سيتم وضع جميع التعليمات البرمجية التالية داخل ملف app.js
، كل قصاصة موضوعة واحدة تلو الأخرى.
أول شيء سأفعله هو طلب حزمة Axios التي قمنا بتثبيتها مسبقًا
const axios = require('axios');
لدينا الآن وصول إلى Axios ، ويمكننا تقديم طلبات HTTP ذات الصلة ، عبر axios
الثابت.
بشكل عام ، ستكون استدعاءات واجهة برمجة التطبيقات لدينا ديناميكية - في هذه الحالة ، قد نرغب في إدخال رموز بريدية ورموز دول مختلفة في عنوان URL الخاص بنا. لذلك ، سأقوم بإنشاء متغيرات ثابتة لكل جزء من عنوان URL ، ثم أجمعها مع سلاسل قوالب ES6. أولاً ، لدينا جزء من عنوان URL الخاص بنا والذي لن يتغير أبدًا مثل مفتاح API الخاص بنا:
const API_URL = 'https://api.openweathermap.org/data/2.5/weather?zip='; const API_KEY = 'Your API Key Here';
سأقوم أيضًا بتعيين الرمز البريدي الخاص بنا ورمز البلد. نظرًا لأننا لا نتوقع مدخلات من المستخدم ونعمل على ترميز البيانات بشكل صارم ، سأجعلها ثابتة أيضًا ، على الرغم من أنه في كثير من الحالات ، سيكون من المفيد أكثر استخدام let
.
const LOCATION_ZIP_CODE = '90001'; const COUNTRY_CODE = 'us';
نحتاج الآن إلى تجميع هذه المتغيرات معًا في عنوان URL واحد يمكننا استخدام Axios لتقديم طلبات GET من أجل:
const ENTIRE_API_URL = `${API_URL}${LOCATION_ZIP_CODE},${COUNTRY_CODE}&appid=${API_KEY}`;
فيما يلي محتويات ملف app.js
الخاص بنا حتى هذه النقطة:
const axios = require('axios'); // API specific settings. const API_URL = 'https://api.openweathermap.org/data/2.5/weather?zip='; const API_KEY = 'Your API Key Here'; const LOCATION_ZIP_CODE = '90001'; const COUNTRY_CODE = 'us'; const ENTIRE_API_URL = `${API_URL}${LOCATION_ZIP_CODE},${COUNTRY_CODE}&appid=${API_KEY}`;
كل ما تبقى للقيام به هو استخدام axios
فعليًا لتقديم طلب GET إلى عنوان URL هذا. لذلك ، سنستخدم طريقة get(url)
المقدمة من axios
.
axios.get(ENTIRE_API_URL)
axios.get(...)
في الواقع وعدًا ، وستتخذ وظيفة رد الاتصال بنجاح وسيطة استجابة تتيح لنا الوصول إلى الاستجابة من واجهة برمجة التطبيقات - وهو نفس الشيء الذي رأيته في المتصفح. سأضيف أيضًا جملة .catch()
للقبض على أي أخطاء.
axios.get(ENTIRE_API_URL) .then(response => console.log(response)) .catch(error => console.log('Error', error));
إذا قمنا الآن بتشغيل هذا الكود مع node app.js
في المحطة الطرفية ، فستتمكن من رؤية الاستجابة الكاملة التي نحصل عليها. ومع ذلك ، لنفترض أنك تريد فقط معرفة درجة حرارة هذا الرمز البريدي - فمعظم تلك البيانات في الاستجابة ليست مفيدة لك. يُرجع Axios بالفعل الاستجابة من واجهة برمجة التطبيقات في كائن البيانات ، وهي خاصية للاستجابة. هذا يعني أن الاستجابة من الخادم موجودة بالفعل في response.data
، لذلك دعونا نطبع ذلك بدلاً من ذلك في وظيفة رد الاتصال: console.log(response.data)
.
الآن ، قلنا أن خوادم الويب تتعامل دائمًا مع JSON كسلسلة ، وهذا صحيح. قد تلاحظ ، مع ذلك ، أن response.data
عبارة عن كائن بالفعل (يتضح من تشغيل console.log(typeof response.data)
) - لم يكن علينا تحليله باستخدام JSON.parse()
. ذلك لأن Axios يهتم بالفعل بهذا الأمر من وراء الكواليس.
يمكن تنسيق الإخراج في المحطة الطرفية من تشغيل console.log(response.data)
- "مطبوع بشكل جيد" - عن طريق تشغيل console.log(JSON.stringify(response.data, undefined, 2))
. JSON.stringify()
بتحويل كائن JSON إلى سلسلة ، وتأخذ الكائن ، والمرشح ، وعدد الأحرف التي يمكن من خلالها وضع مسافة بادئة عند الطباعة. يمكنك رؤية الرد الذي يوفر:
{ "coord": { "lon": -118.24, "lat": 33.97 }, "weather": [ { "id": 800, "main": "Clear", "description": "clear sky", "icon": "01d" } ], "base": "stations", "main": { "temp": 288.21, "pressure": 1022, "humidity": 15, "temp_min": 286.15, "temp_max": 289.75 }, "visibility": 16093, "wind": { "speed": 2.1, "deg": 110 }, "clouds": { "all": 1 }, "dt": 1546459080, "sys": { "type": 1, "id": 4361, "message": 0.0072, "country": "US", "sunrise": 1546441120, "sunset": 1546476978 }, "id": 420003677, "name": "Lynwood", "cod": 200 }
الآن ، من الواضح أن درجة الحرارة التي نبحث عنها تقع في الخاصية main
لكائن response.data
، لذا يمكننا الوصول إليها عن طريق استدعاء response.data.main.temp
. لنلقِ نظرة على رمز التطبيق حتى الآن:
const axios = require('axios'); // API specific settings. const API_URL = 'https://api.openweathermap.org/data/2.5/weather?zip='; const API_KEY = 'Your API Key Here'; const LOCATION_ZIP_CODE = '90001'; const COUNTRY_CODE = 'us'; const ENTIRE_API_URL = `${API_URL}${LOCATION_ZIP_CODE},${COUNTRY_CODE}&appid=${API_KEY}`; axios.get(ENTIRE_API_URL) .then(response => console.log(response.data.main.temp)) .catch(error => console.log('Error', error));
درجة الحرارة التي نحصل عليها هي في الواقع بمقياس كلفن ، وهو مقياس درجة حرارة يُستخدم عمومًا في الفيزياء والكيمياء والديناميكا الحرارية نظرًا لأنه يوفر نقطة "الصفر المطلق" ، وهي درجة الحرارة التي عندها كل الحركة الحرارية لجميع تتوقف الجسيمات. نحتاج فقط إلى تحويل هذا إلى فهرنهايت أو سيليسيوس باستخدام الصيغ أدناه:
F = K * 9/5 - 459.67
ج = ك - 273.15
لنقم بتحديث رد الاتصال بنجاح لطباعة البيانات الجديدة بهذا التحويل. سنضيف أيضًا جملة مناسبة لأغراض تجربة المستخدم:
axios.get(ENTIRE_API_URL) .then(response => { // Getting the current temperature and the city from the response object. const kelvinTemperature = response.data.main.temp; const cityName = response.data.name; const countryName = response.data.sys.country; // Making K to F and K to C conversions. const fahrenheitTemperature = (kelvinTemperature * 9/5) — 459.67; const celciusTemperature = kelvinTemperature — 273.15; // Building the final message. const message = ( `Right now, in \ ${cityName}, ${countryName} the current temperature is \ ${fahrenheitTemperature.toFixed(2)} deg F or \ ${celciusTemperature.toFixed(2)} deg C.`.replace(/\s+/g, ' ') ); console.log(message); }) .catch(error => console.log('Error', error));
الأقواس حول متغير message
غير مطلوبة ، إنها تبدو لطيفة فحسب - تشبه عند العمل مع JSX في React. تعمل الخطوط المائلة العكسية على إيقاف سلسلة القالب من تنسيق سطر جديد ، وتتخلص طريقة replace()
String prototype من المساحة البيضاء باستخدام التعبيرات العادية (RegEx). تعمل أساليب النموذج الأولي toFixed()
Number على تقريب عدد عشري إلى عدد محدد من المنازل العشرية - في هذه الحالة ، اثنان.
مع ذلك ، يبدو app.js
النهائي لدينا على النحو التالي:
const axios = require('axios'); // API specific settings. const API_URL = 'https://api.openweathermap.org/data/2.5/weather?zip='; const API_KEY = 'Your API Key Here'; const LOCATION_ZIP_CODE = '90001'; const COUNTRY_CODE = 'us'; const ENTIRE_API_URL = `${API_URL}${LOCATION_ZIP_CODE},${COUNTRY_CODE}&appid=${API_KEY}`; axios.get(ENTIRE_API_URL) .then(response => { // Getting the current temperature and the city from the response object. const kelvinTemperature = response.data.main.temp; const cityName = response.data.name; const countryName = response.data.sys.country; // Making K to F and K to C conversions. const fahrenheitTemperature = (kelvinTemperature * 9/5) — 459.67; const celciusTemperature = kelvinTemperature — 273.15; // Building the final message. const message = ( `Right now, in \ ${cityName}, ${countryName} the current temperature is \ ${fahrenheitTemperature.toFixed(2)} deg F or \ ${celciusTemperature.toFixed(2)} deg C.`.replace(/\s+/g, ' ') ); console.log(message); }) .catch(error => console.log('Error', error));
خاتمة
لقد تعلمنا الكثير حول كيفية عمل Node في هذه المقالة ، من الاختلافات بين الطلبات المتزامنة وغير المتزامنة ، إلى وظائف رد الاتصال ، إلى ميزات ES6 الجديدة ، والأحداث ، ومديرو الحزم ، وواجهات برمجة التطبيقات ، و JSON ، وبروتوكول نقل النص التشعبي ، وقواعد البيانات غير العلائقية ، وقمنا حتى ببناء تطبيق سطر الأوامر الخاص بنا باستخدام معظم تلك المعرفة الجديدة التي تم العثور عليها.
في المقالات المستقبلية في هذه السلسلة ، سنلقي نظرة متعمقة على Call Stack ، و Event Loop ، و Node APIs ، وسنتحدث عن مشاركة الموارد عبر الأصول (CORS) ، وسنقوم ببناء Stack Bookshelf API باستخدام قواعد البيانات ونقاط النهاية ومصادقة المستخدم والرموز المميزة وعرض القالب من جانب الخادم والمزيد.
من هنا ، ابدأ في إنشاء تطبيقات Node الخاصة بك ، واقرأ وثائق Node ، وانطلق وابحث عن واجهات برمجة تطبيقات مثيرة للاهتمام أو وحدات Node Modules وقم بتنفيذها بنفسك. العالم هو محارتك ولديك وصول في متناول يدك إلى أكبر شبكة معرفة على هذا الكوكب - الإنترنت. استخدامه لصالحك.
مزيد من القراءة على SmashingMag:
- فهم واستخدام REST APIs
- ميزات JavaScript الجديدة التي ستغير طريقة كتابة Regex
- الحفاظ على سرعة Node.js: الأدوات والتقنيات والنصائح لإنشاء خوادم Node.js عالية الأداء
- بناء روبوت محادثة AI بسيط باستخدام واجهة برمجة تطبيقات Web Speech و Node.js