نظرة عامة حول الحظر مقابل عدم الحظر
تغطي هذه النظرة العامة الفرق بين استدعاءات الحظر وعدم الحظر في Node.js. ستشير هذه النظرة العامة إلى حلقة الأحداث و libuv ولكن لا يلزم معرفة مسبقة بهذه الموضوعات. يُفترض أن لدى القراء فهمًا أساسيًا للغة JavaScript ونمط استدعاء JavaScript غير المتزامن Node.js.
INFO
تشير "الإدخال/الإخراج" بشكل أساسي إلى التفاعل مع قرص النظام والشبكة المدعومة من libuv.
حظر
الحظر هو عندما يجب أن ينتظر تنفيذ JavaScript إضافي في عملية Node.js حتى تكتمل عملية غير JavaScript. يحدث هذا لأن حلقة الأحداث غير قادرة على الاستمرار في تشغيل JavaScript أثناء حدوث عملية حظر.
في Node.js، لا يشار عادةً إلى JavaScript الذي يُظهر أداءً ضعيفًا بسبب كونه مكثفًا لوحدة المعالجة المركزية بدلاً من الانتظار لعملية غير JavaScript، مثل الإدخال/الإخراج، على أنه حظر. تعد الطرق المتزامنة في مكتبة Node.js القياسية التي تستخدم libuv هي عمليات الحظر الأكثر استخدامًا. قد تحتوي الوحدات النمطية الأصلية أيضًا على طرق حظر.
توفر جميع طرق الإدخال/الإخراج في مكتبة Node.js القياسية إصدارات غير متزامنة، وهي غير حظر، وتقبل وظائف الاستدعاء. تحتوي بعض الطرق أيضًا على نظائر حظر، والتي لها أسماء تنتهي بـ Sync
.
مقارنة التعليمات البرمجية
تقوم طرق الحظر بالتنفيذ بشكل متزامن وتقوم طرق عدم الحظر بالتنفيذ بشكل غير متزامن.
باستخدام وحدة نظام الملفات كمثال، هذه قراءة ملف متزامنة:
const fs = require('node:fs')
const data = fs.readFileSync('/file.md') // يحظر هنا حتى تتم قراءة الملف
وهنا مثال غير متزامن مكافئ:
const fs = require('node:fs')
fs.readFile('/file.md', (err, data) => {
if (err) throw err
})
يبدو المثال الأول أبسط من المثال الثاني ولكنه يعيبه أن السطر الثاني يحظر تنفيذ أي JavaScript إضافي حتى تتم قراءة الملف بأكمله. لاحظ أنه في الإصدار المتزامن، إذا تم طرح خطأ، فسيلزم التقاطه وإلا ستتعطل العملية. في الإصدار غير المتزامن، الأمر متروك للمؤلف ليقرر ما إذا كان يجب طرح خطأ كما هو موضح.
دعنا نوسع مثالنا قليلاً:
const fs = require('node:fs')
const data = fs.readFileSync('/file.md') // يحظر هنا حتى تتم قراءة الملف
console.log(data)
moreWork() // سيتم تشغيله بعد console.log
وهنا مثال غير متزامن مشابه، ولكنه غير مكافئ:
const fs = require('node:fs')
fs.readFile('/file.md', (err, data) => {
if (err) throw err
console.log(data)
})
moreWork() // سيتم تشغيله قبل console.log
في المثال الأول أعلاه، سيتم استدعاء console.log
قبل moreWork()
. في المثال الثاني، fs.readFile()
غير حظر بحيث يمكن أن يستمر تنفيذ JavaScript وسيتم استدعاء moreWork()
أولاً. تعد القدرة على تشغيل moreWork()
دون انتظار اكتمال قراءة الملف خيار تصميم رئيسي يسمح بإنتاجية أعلى.
التزامن والإنتاجية
تنفيذ JavaScript في Node.js هو ذو مسار تنفيذ واحد، لذا يشير التزامن إلى قدرة حلقة الأحداث على تنفيذ وظائف رد نداء JavaScript بعد الانتهاء من أعمال أخرى. يجب أن يسمح أي رمز يُتوقع تشغيله بطريقة متزامنة لحلقة الأحداث بالاستمرار في التشغيل حيث تحدث عمليات غير JavaScript، مثل الإدخال/الإخراج.
على سبيل المثال، دعنا نفكر في حالة تستغرق فيها كل عملية طلب إلى خادم الويب 50 مللي ثانية لإكمالها، و45 مللي ثانية من تلك الـ 50 مللي ثانية هي إدخال/إخراج قاعدة بيانات يمكن إجراؤه بشكل غير متزامن. يتيح اختيار عمليات غير حظر غير متزامنة تحرير تلك الـ 45 مللي ثانية لكل طلب للتعامل مع طلبات أخرى. هذا فرق كبير في السعة فقط عن طريق اختيار استخدام طرق غير حظر بدلاً من طرق الحظر.
تختلف حلقة الأحداث عن النماذج في العديد من اللغات الأخرى حيث قد يتم إنشاء سلاسل تنفيذ إضافية للتعامل مع العمل المتزامن.
مخاطر خلط كود الحظر وغير الحظر
هناك بعض الأنماط التي يجب تجنبها عند التعامل مع الإدخال/الإخراج. دعنا نلقي نظرة على مثال:
const fs = require('node:fs')
fs.readFile('/file.md', (err, data) => {
if (err) throw err
console.log(data)
})
fs.unlinkSync('/file.md')
في المثال أعلاه، من المحتمل أن يتم تشغيل fs.unlinkSync()
قبل fs.readFile()
، مما سيحذف file.md
قبل قراءته بالفعل. طريقة أفضل لكتابة هذا، وهي غير حظر تمامًا ومضمونة التنفيذ بالترتيب الصحيح هي:
const fs = require('node:fs')
fs.readFile('/file.md', (readFileErr, data) => {
if (readFileErr) throw readFileErr
console.log(data)
fs.unlink('/file.md', unlinkErr => {
if (unlinkErr) throw unlinkErr
})
})
يضع ما سبق استدعاء غير حظر لـ fs.unlink()
داخل رد نداء fs.readFile()
مما يضمن الترتيب الصحيح للعمليات.