نظرة عامة على الطرق المُعطلة مقابل الطرق غير المُعطلة
تغطي هذه النظرة العامة الفرق بين النداءات المُعطلة وغير المُعطلة في Node.js. ستشير هذه النظرة العامة إلى حلقة الأحداث و libuv ولكن لا يلزم وجود معرفة مسبقة بهذه المواضيع. يُفترض أن يكون لدى القراء فهم أساسي للغة JavaScript ونمط الاستدعاء Node.js.
INFO
تشير "I/O" في المقام الأول إلى التفاعل مع قرص النظام وشبكته المدعومة بواسطة libuv.
الطرق المُعطلة
الطرق المُعطلة هي عندما يجب أن ينتظر تنفيذ JavaScript إضافي في عملية Node.js حتى تكتمل عملية غير JavaScript. يحدث هذا لأن حلقة الأحداث غير قادرة على الاستمرار في تشغيل JavaScript أثناء حدوث عملية معطلة.
في Node.js، لا يُشار عادةً إلى JavaScript الذي يُظهر ضعفًا في الأداء بسبب كثافة وحدة المعالجة المركزية بدلاً من الانتظار لعملية غير JavaScript، مثل I/O، على أنه معطل. تعتبر الأساليب المتزامنة في مكتبة Node.js القياسية التي تستخدم libuv هي عمليات معطلة شائعة الاستخدام. قد تحتوي الوحدات الأصلية أيضًا على طرق معطلة.
تقدم جميع طرق I/O في مكتبة 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()
دون انتظار اكتمال قراءة الملف هو خيار تصميم أساسي يسمح بزيادة الإنتاجية.
التزامن والإنتاجية
تنفيذ جافا سكريبت في نود.جي إس أحادي الخيط، لذا يشير التزامن إلى قدرة حلقة الأحداث على تنفيذ دوال استدعاء جافا سكريبت بعد إتمام أعمال أخرى. يجب أن يسمح أي كود يُتوقع تشغيله بشكل متزامن لحلقة الأحداث بالاستمرار في التشغيل أثناء حدوث عمليات غير جافا سكريبت، مثل I/O.
كمثال، دعونا نفكر في حالة يستغرق فيها كل طلب إلى خادم ويب 50 مللي ثانية لإتمامه و 45 مللي ثانية من تلك الـ 50 مللي ثانية هي I/O قاعدة بيانات يمكن إجراؤها بشكل غير متزامن. إن اختيار عمليات غير مُحجِزة غير متزامنة يُحرر تلك الـ 45 مللي ثانية لكل طلب للتعامل مع طلبات أخرى. هذا فرق كبير في السعة بمجرد اختيار استخدام طرق غير مُحجِزة بدلاً من الطرق المُحجِزة.
تختلف حلقة الأحداث عن النماذج في العديد من اللغات الأخرى حيث يمكن إنشاء خيوط إضافية للتعامل مع العمل المتزامن.
مخاطر مزج التعليمات البرمجية المُحجِزة وغير المُحجِزة
هناك بعض الأنماط التي يجب تجنبها عند التعامل مع I/O. دعونا نلقي نظرة على مثال:
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()
مما يضمن الترتيب الصحيح للعمليات.