مقدمة في البرمجة غير المتزامنة في JavaScript
JavaScript هي لغة برمجة أحادية الخيط مع نموذج غير مانع يعتمد على الأحداث. في بيئة Node.js، البرمجة غير المتزامنة ليست مجرد خيار مناسب، بل ضرورة أساسية. بدون البرمجة غير المتزامنة، سيتوقف تطبيق الخادم عند كل طلب إلى قاعدة البيانات، أو قراءة ملف، أو استدعاء شبكي، مما يجعله غير صالح للعمل الفعلي.
في هذا الدليل، سنشرح النماذج الثلاثة الرئيسية للبرمجة غير المتزامنة في JavaScript: Callback و Promise و async/await. ستتعلم كيف يعمل Event Loop، وكيفية معالجة الأخطاء، وكتابة كود قابل للقراءة وعالي الأداء لـ Node.js.
1. Callback: أساس البرمجة غير المتزامنة
Callback (دالة الاستدعاء) هي دالة تُمرر إلى دالة أخرى كوسيطة وتُستدعى بعد اكتمال العملية غير المتزامنة. هذا هو أقدم وأبسط نموذج للبرمجة غير المتزامنة في JavaScript.
مثال بسيط مع Callback
const fs = require('fs');
// قراءة ملف غير متزامنة مع callbackfs.readFile('example.txt', 'utf8', (err, data) => { if (err) { console.error('خطأ في قراءة الملف:', err); return; } console.log('محتوى الملف:', data);});
console.log('تم بدء قراءة الملف...');// الإخراج: "تم بدء قراءة الملف..." سيظهر قبل محتوى الملففي هذا المثال، سيتم تنفيذ دالة callback فقط بعد قراءة الملف بالكامل. في هذه الأثناء، يستمر الخيط الرئيسي في التنفيذ — وهذا هو السلوك غير المانع.
Callback Hell (جحيم الاستدعاءات)
العيب الرئيسي للـ callbacks هو فقدان قابلية القراءة بسرعة عند الاستدعاءات المتداخلة:
// جحيم الاستدعاءاتgetUser(1, (err, user) => { if (err) throw err; getPosts(user.id, (err, posts) => { if (err) throw err; getComments(posts[0].id, (err, comments) => { if (err) throw err; console.log(user.name, posts[0].title, comments); }); });});من الصعب تصحيح هذا الكود وصيانته وتوسيعه. لحسن الحظ، تقدم JavaScript الحديثة حلولاً أكثر أناقة بكثير.
2. Promise: وعد بالنتيجة
Promise (الوعد) هو كائن يمثل نتيجة عملية غير متزامنة، يمكن أن تكون مكتملة (fulfilled) أو مرفوضة (rejected) أو في حالة انتظار (pending). تسمح الـ Promises بتجنب التداخل وبناء سلاسل من الاستدعاءات.
إنشاء واستخدام Promise
// دالة تُرجع Promisefunction readFilePromise(filename) { return new Promise((resolve, reject) => { const fs = require('fs'); fs.readFile(filename, 'utf8', (err, data) => { if (err) { reject(err); // خطأ — تم رفض الوعد } else { resolve(data); // نجاح — تم تنفيذ الوعد } }); });}
// استخدام الوعدreadFilePromise('example.txt') .then(data => { console.log('المحتوى:', data); return data.length; }) .then(length => { console.log('طول النص:', length); }) .catch(err => { console.error('خطأ:', err); });طرق Promise.all و Promise.race
تسمح Promise.all بانتظار تنفيذ جميع الـ promises، بينما تسمح Promise.race بانتظار أول promise مكتمل:
const p1 = readFilePromise('file1.txt');const p2 = readFilePromise('file2.txt');const p3 = readFilePromise('file3.txt');
// ننتظر جميع الملفات الثلاثةPromise.all([p1, p2, p3]) .then(([data1, data2, data3]) => { console.log('تم تحميل جميع الملفات'); console.log(data1, data2, data3); }) .catch(err => console.error('خطأ في أحد الملفات:', err));
// ننتظر أول ملف مكتملPromise.race([p1, p2, p3]) .then(firstResult => { console.log('تم تحميل الملف الأول:', firstResult); });