ما هي المزامنة في لغة JavaScript
خلونا ندخل في الموضوع على طول. لما نتكلم عن المزامنة (Asynchronicity) في JavaScript، إحنا بنتكلم عن قدرة الجافاسكريبت إنها تنفذ مهام متعددة بدون ما تنتظر مهمة تخلص عشان تبدأ الثانية. فكر فيها كأنك بتطلب قهوة في كافيه. لو كان الكافيه متزامن (Synchronous)، الباريستا ما يقدر يبدأ يسوي قهوة للزبون اللي بعدك إلا لما تخلص قهوتك وتدفع وتمشي. أما لو كان غير متزامن (Asynchronous)، الباريستا ياخذ طلبك، ويبدأ يسوي قهوتك، وفي نفس الوقت ياخذ طلب الزبون اللي بعدك ويبدأ يسوي قهوته كمان.
ليش نحتاجها؟
تخيل إنك بتجيب بيانات من سيرفر (مثل API call) أو بتسوي عملية بتاخذ وقت طويل (زي قراءة ملف كبير). لو الجافاسكريبت كانت متزامنة بس، كان المتصفح بيتعلق (freezes) وما تقدر تسوي أي حاجة لحد ما تخلص العملية دي.
المزامنة بتخلي الكود بتاعك غير محظور (non-blocking)، يعني تقدر تسوي عمليات طويلة في الخلفية بدون ما توقف واجهة المستخدم (UI) عن الاستجابة.
كيف تشتغل المزامنة في JavaScript؟
الجافاسكريبت نفسها لغة متزامنة وذات خيط واحد (single-threaded synchronous language). إيه ده؟ كيف؟
السر مو في الجافاسكريبت نفسها، بل في البيئة اللي بتشتغل فيها (المتصفح أو Node.js). البيئات دي بتوفر لنا Web APIs (مثل setTimeout، fetch، DOM events) اللي بتشتغل خارج نطاق الجافاسكريبت الرئيسي.
فيه مفاهيم أساسية هنا:
Call Stack: المكان اللي بيتنفذ فيه الكود بتاعك سطر بسطر.Web APIs: وظائف بتوفرها البيئة (المتصفح) لتنفيذ مهام غير متزامنة.Callback Queue(Task Queue): لما الـWeb APIتخلص شغلها، بتحط الـcallback functionبتاعها هنا.Event Loop: هو اللي بيراقب الـCall StackوالـCallback Queue. لو الـCall Stackفاضي، الـEvent Loopبياخد أولcallbackمن الـCallback Queueويحطه في الـCall Stackعشان يتنفذ.
أدوات التعامل مع المزامنة
1. Callbacks (الدوال الراجعة)
أبسط طريقة للتعامل مع المزامنة. هي دالة بنمررها لدالة ثانية عشان تتنفذ بعد ما تخلص المهمة الأولى.
ملاحظة: الكولباكس ممكن تؤدي لمشكلة اسمها "
Callback Hell" لما يكون عندك كولباكس متداخلة كتير.
مثال:
setTimeout(function() {
console.log('هذي الرسالة حتظهر بعد ثانيتين');
}, 2000);
console.log('هذي الرسالة حتظهر أولاً');
في المثال ده، console.log('هذي الرسالة حتظهر أولاً') حتتنفذ على طول، وبعد ثانيتين حتتنفذ الدالة اللي داخل setTimeout.
2. Promises (الوعود)
حل أفضل لمشكلة Callback Hell. الـ Promise هو كائن بيمثل نتيجة عملية غير متزامنة ممكن تكون نجحت (fulfilled) أو فشلت (rejected) أو لسه في انتظار (pending).
مثال:
const fetchData = new Promise((resolve, reject) => {
// محاكاة لعملية جلب بيانات من سيرفر
setTimeout(() => {
const success = true; // نفترض إن العملية نجحت
if (success) {
resolve('البيانات تم جلبها بنجاح!');
} else {
reject('فشل في جلب البيانات.');
}
}, 1500);
});
fetchData
.then(data => {
console.log(data); // "البيانات تم جلبها بنجاح!"
})
.catch(error => {
console.error(error); // "فشل في جلب البيانات."
})
.finally(() => {
console.log('انتهت عملية جلب البيانات.');
});
console.log('جاري جلب البيانات...');
هنا، console.log('جاري جلب البيانات...') حتظهر أولاً، وبعدين الـ Promise حيتعامل مع جلب البيانات في الخلفية، ولما يخلص حيتنفذ .then() أو .catch().
3. Async/Await
أحدث وأفضل طريقة للتعامل مع الـ Promises بطريقة تخلي الكود يبدو وكأنه متزامن (synchronous) وسهل القراءة. async بتخلي الدالة ترجع Promise، وawait بتوقف تنفيذ الدالة لحد ما الـ Promise اللي بتنتظره يرجع نتيجة.
ملاحظة:
awaitيمكن استخدامها فقط داخل دالة معلمة بـasync.
مثال:
async function getDataAsync() {
console.log('بدأ جلب البيانات...');
try {
const response = await fetch('https://api.example.com/data'); // افترض هذا API حقيقي
const data = await response.json();
console.log('البيانات المستلمة:', data);
} catch (error) {
console.error('حدث خطأ:', error);
} finally {
console.log('انتهت دالة جلب البيانات.');
}
}
getDataAsync();
console.log('الكود يستمر في التنفيذ بعد استدعاء getDataAsync.');
الـ fetch هنا عملية غير متزامنة، لكن باستخدام await، الكود داخل getDataAsync بيبدو وكأنه بيتنفذ سطر بسطر.
خلاصة
المزامنة في JavaScript هي أساس بناء تطبيقات ويب حديثة وسريعة الاستجابة. فهمك لكيفية عمل الـ Event Loop واستخدامك الفعال للـ Callbacks، Promises، وAsync/Await حيخليك تكتب كود أنظف وأكثر كفاءة.