جحيم الـ Callback
السلام عليكم ورحمة الله وبركاته
الفهرس
المقدمة
سنشرح في هذا الدرس مفهوم الـ callback
في الجافاسكريبت
والأمور التي تدور حوله ومشاكله
ما هي دالة الـ callback ؟
أولًا يجب أن نعرف أن دالة الـ callback
هي دالة تقوم باستقبال دالة أخرى كـ argument
ثم يتم استدعاءها في أي مكان داخل الدالة
الأمر ليس بأن تستدعي الدالة داخل الـ argument
، بل أن ترسل الدالة بذاتها كـ argument
لدالة أخرى، ما الفرق ؟
النظرة الخاطئة عن الـ callback
حسنا انتبه في المثال التالي
function sum(x, y) {
return x + y;
}
function print(s) {
console.log(s);
}
هنا لدينا دالة تدعى sum
تقوم بارجاع مجموع الرقمين ودالة تدعى print
تقوم باستقبال قيمة وتطبعها
عندما تستقبل دالة print
دالة sum
كهذا
print(sum(5, 10)); // OUTPUT: 15
هل هذا يسمى callback
؟ الاجابة بكل بساطة لا
لان دالة print
لم تستقبل دالة sum
بل هي فقط استقبلت ناتج دالة sum
بمعنى انها استقبلت رقم وليس دالة
المفهوم الصحيح لدالة الـ callback
الـ callback
هو أن ترسل الدالة نفسها وليس أن ترسل ناتج استدعاءها
إن أرسلت الدالة نفسها فيمكنك أن تستدعيها داخل الدالة
بمعنى أننا سنرسل اسم الدالة كهذا
print(sum);
نحن هنا أرسلنا الدالة نفسها دون أن نستدعيها بـ ()
ونستطيع أن نستدعيها داخل دالة الـ print
كهذا
function sum(x, y) {
return x + y;
}
function print(s) {
let result = s(5, 10); // call the function that we passed
console.log(result);
}
print(sum); // return 15
هنا عندما أرسلنا دالة sum
إلى دالة print
دون استدعاءها، بهذا الشكل print(sum)
أرسلنا فقط اسم الدالة دون ()
كما تلاحظ
اصبح الآن معنا reference
داخل دالة الـ print
يمثل دالة الـ sum
وكل ما فعلناه داخل الـ print
هو أننا استدعينا دالة s
التي تمثل دالة الـ sum
بالطبع
هنا الـ s
تحتاج لقيمتين عند استدعاءها
لان الـ sum
كانت تستقبل قيمتين عند استدعاءه
لذا ارسلنا للـ s
قيمتين الـ x, y
هكذا s(x, y)
كما يريد
بعد ما استدعينا s(x, y)
وارسلنا القيم سيتم تنفيذ محتوى دالة الـ sum
وهي جمع القيمتين return x + y;
ثم استقبلنا الناتج في متغير result
هكذا let result = s(5, 10);
وطبعناه console.log(result);
لاحظ أننا استدعينا دالة الـ s
التي نفذت محتوى دالة sum
لانه كما قلنا دالة s
اصبحت reference
لدالة الـ sum
وقمنا بإرسال أي قيم ثم طبعنا الناتج
الشاهد من المثال أننا قمنا بإرسال دالة واستدعيناها في أي مكان داخل الدالة
ملحوظة
: عليك أن تنتبه إلى عدد المتغيرات التي يقبلها الـcallback
بمعنى أننا لو افترضنا أن الـsum
تستقبل3
متغيرات فقطx, y, z
والـcallback
يستقبل2
فقط كما رأينا هناs(x, y)
يمكن أن يكون لديكcallback
يستقبل متغير واحد فقط أو5
مثلًا أو لا يستقبل شيء فجيب أن تعرف طبيعة عمل الـcallback
الذي أمامك
أمثلة على الـ callback
سنستعرض بضع أمثلة لتوضيح الأمر أكثر
المثال الأول
function sum(x, y, fun) {
let result = x + y;
return fun(result);
}
function multipleBy5(n) {
return n * 5;
}
let result = sum(5, 10, multipleBy5);
console.log(result);
لناخذ هذا المثال خطوة خطوة
لدينا دالة تدعى sum
تستقبل قيمتين x, y
وتستقبل callback
يدعى fun
وكل ما تفعله الدالة هو جمع القيمتين في متغير result
ثم نستدعى دالة fun
التي تستقبل المجموع return fun(result);
ثم نرجع القيمة الراجعة من fun
نلاحظ هنا أن الـ fun
هي دالة callback
تحتاج لاستقبال متغير واحد ويبدو أنها تقوم بإرجاع قيمة ما
لذا نحتاج لدالة تستقبل متغير وترجع قيمة
وهذا ما فعلناه مع دالة multipleBy5
التي تستقبل قيمة عددية ثم ترجعه مضروبًا في 5
هل يمكنك أن تأخذ 5
دقائق وتحلل هذا السطر const result = sum(5, 10, multipleBy5);
بالمعطيات التي أخرجانها للتو ؟
هذا اختبار صغير يمكنك أن تتوقف عن القراءة وتفكر بتتبع المثال الذي بالأعلى ومحاولة فهمه بنفسك ثم أرجع وأكمل القراءة
هنا ببساطة sum(5, 10, multipleBy5);
قمنا بإعطاء قيم 5, 10
إلى x, y
ثم أرسلنا multipleBy5
لتكون دالة callback
سندخل الآن داخل دالة الـ sum
لنرى ماذا سيحدثlet result = x + y;
أول شيء يقوم بجمع القيم وتخزينها في result
قيمة result
الآن قيمتها 15
ثم نستدعي fun
التي في حالتنا الآن أصبحت reference
لدالة الـ multipleBy5
ثم أرسلنا قيمة الجمع لها fun(result)
الآن تم استدعاء دالة multipleBy5
عن طريق fun
الـ reference
الخاص بها
دالة multipleBy5
ستستقبل قيمة المجموع ثم تضربه في 5
وترجع القيمة return n * 5;
لذا القيمة الراجعة الآن من multipleBy5
هي 15 * 5 = 75
لذا نعود لدالة الـ sum
، والآن هنا return fun(result);
القيمة الراجعة من fun
ستكون 75
وستقوم دالة الـ sum
بإرجاع القيمة لتكون قيمة الـ result
هنا let result = sum(5, 10, multipleBy5);
تساوي 75
الأمر قد يكون يحتوي على لف ودوران قليلًا لكن بالتتبع والاعتياد سيكون الأمر مع الوقت عاديًا بالنسبة لك مثل سائر الأمور
أريدك أن تستوعب طريقة استخدامنا للـ callback
في المثال السابق فقط
المثال الثاني
function sum(x, y) {
return x + y;
}
function applyOperation(x, y, fun) {
let result = fun(x, y); // call the function that we pass it
return result;
}
// we pass sum as function, and didn't call it
let result = applyOperation(5, 10, sum); // return 15
console.log(result); // OUTPUT: 15
هنا نفس المثال السابق تمامًا لكن هنا لدينا applyOperation(x, y, fun)
تستقبل القيم x ,y
التي ستُرسل للدالة fun
التي ستستقبلها كذلك
ثم تستدعي الدالة هكذا fun(x, y)
وترسل القيم لها ثم نرجع الناتج ببساطة
المثال بسيط لكن كيف نستفيد من هذا
الموضوع يحتاج لتوسيع نطاق تفكيرك لا أكثر
لاحظ في هذا السطر let result = fun(x, y);
دالة الـ fun
كانت تمثل دالة sum
التي أرسلناها ودالة الـ sum
كانت تستقبل قيمتين لذا دالة fun
أصبحت كذلك
هنا الـ fun
كانت نسخة طبق الأصل للـ sum
التي أرسلناها
وستكون نسخة طبق الأصل لأي دالة سنرسلها، فالأمر لا يختصر على sum
بمعمى أننا نستطيع استخدام أكثر من دالة كـ callback
كيف ذلك ؟ ركز معي هنا جيدًا
دعونا نرى توسعة أكبر للكود لنفهمه بشكل أوضح
أنظر هنا
function sum(x, y) {
return x + y;
}
function sub(x, y) {
return x - y;
}
function mul(x, y) {
return x * y;
}
function div(x, y) {
return x / y;
}
هنا لدينا أكثر من دالة، يمكننا أن تتوقع ما الذي سنفعله الآن
نستطيع هنا أن نرسل أي دالة منهم كـ callback
لدالة الـ applyOperation
function applyOperation(x, y, fun) {
let result = fun(x, y); // call the function that we pass it
return result;
}
// we pass multiple functions to the callback
applyOperation(5, 10, sum); // return 15
applyOperation(5, 10, sub); // return -5
applyOperation(5, 10, mul); // return 50
applyOperation(5, 10, div); // return 0.5
هنا كما نلاحظ دالة applyOperation
كل مرة تستقبل دالة callback
مختلفة
أظنك الآن فهمت الفكرة العامة للـ callback
إن شاء الله
إنشاء الدالة داخل الـ argument ؟
هنا في ميزة جميلة جدًا يمكننا عملها مع الـ callback
وهو بدلًا من أن ننشيء دالة ثم نرسلها كـ callback
نستطيع إنشاء الدالة كـ argument
بشكل مباشر، ما معنى هذا الكلام ؟
تتذكرون المثال السابق هذا
function sum(x, y) {
return x + y;
}
function applyOperation(x, y, fun) {
let result = fun(x, y);
return result;
}
let result = applyOperation(5, 10, sum);
console.log(result); // OUTPUT: 15
تأملوا فيه جيدًا وقارنوه مع ما سنكتبه الآن
function applyOperation(x, y, fun) {
let result = fun(x, y);
return result;
}
let result = applyOperation(5, 10, function (x, y) {
return x + y;
});
console.log(result); // OUTPUT: 15
لاحظ أننا بدل من أن ننشيء دالة sum
خارج الـ applyOperation
ثم نرسلها للدالة كما في المثال الأول
ما قمنا به أننا أنشأنا الدالة نفسها أثناء استدعائنا لدالة applyOperation
وهى تسمى هنا Anonymous Functions
أي دالة بلا إسم، وإن فكرت في الأمر فستجد إن اسم الدالة لن يكون له فائدة هنا
لانها ستستقبل كـ reference
داخل دالة الـ applyOperation
كـ fun
ولن تحتاج لاسم الدالة لأنك لن تستطيع استدعاءها خارج الدالة على كل حال
ويمكنك استخدام أي طريقة تناسب الموقف الذي أنت فيه
إن كنت تريد دالة تستدعيها مرة واحدة فقط كـ callback
ولن تستخدمها مجددًا فاجعلها anonymous function
وإن كنت تريد أن تستخدم الدالة في أماكن أخرى فيما بعد فاستخدم الطريقة الأولى بالطبع
مثال لدالة built-in تستخدم callback
دعونا نستكشف دالة built-in
تستخدم callback
بغرض شرح بعض الأمور حول الـ callback
الدالة التي أريد أن اتكلم عنها هى setTimeout
هي دالة فكرتها بسيطة setTimeout(callback, timeInMilliseconds)
فقط تعطيها callback
ثم تعطيها الوقت الذي سيتم استدعاء الـ callback
فيه
هذا الوقت يكون ميلي ثانية والأف ميلي ثانية يساوي واحد ثانية
تأمل في الكود التالي
console.log('Starting');
setTimeout(function () {
console.log('This will call after 1 sec');
}, 1000); // 1000 ms = 1 sec
إن فهمت فكرة setTimeout
فستعرف انه سيتم طباعة starting
ثم سينتظر ثانية واحدة ليتم طباعة This will call after 1 sec
لأن الـ callback
تم استدعاءه بعد ثانية واحدة
محاكاة عمل setTimeout
أريدك أن تأخذ تحدي بسيط وهو محاولة تخيلية لكيف تم إنشاء دالة setTimeout
ومحاولة عمل واحدة بأنفسنا
هذا اختبار آخر يمكنك أن تتوقف عن القراءة وتفكر بعمل دالة
setTimeout
خاصة بك لمحاولة فهمت طريقة عمل الـcallback
ثم أرجع وأكمل القراءة
حسنًا سأفترض أنك انتهيت الآن، لا تقلق إن لم تستطيع نسخ الدالة وعمل دالة مشابه للـ setTimeout
فلا بأس
فنحن هنا أتينا لنتعلم لذا سأريك الدالة التي قمت بصنعها الآن
الفكرة الأساسية هنا أننا نحتاج لشيء يحدد لنا الوقت لنعرف متى سنستدعي دالة الـ callback
لانه سيتم استدعائها بعد مدة من الزمن كما نعرف
لذا استخدمنا Date.now()
وهي دالة في الجافاسكريبت
داخل الـ Date
تقوم بإرجاع الوقت الحالي بالميلي ثانية
وهذا ما نريده، الآن لنتأمل الكود التالي ونحلله
console.log('starting');
function ourSetTimeout(fun, ms) {
let stopAt = Date.now() + ms;
while (Date.now() < stopAt) continue;
fun();
}
ourSetTimeout(function () {
console.log('done');
}, 1000);
أنشأنا الدالة الخاصة بنا ourSetTimeout(fun, ms)
تستقبل fun
التي ستكون دالة الـ callback
ثم ms
والذي سيكون الوقت بالميلي ثانية
داخل الدالة في السطر التالي let stopAt = Date.now() + ms;
قمنا بإنشاء متغير سميناه stopAt
الذي سيكون كما يوحي الإسم نهاية الوقت أي الوقت الذي سننتهي عنده وهو الوقت الحالي + الوقت الذي حددناه بالميلي ثانية
ثم لدينا while (Date.now() < stopAt) continue;
وهو إذا ترجمته بشكل حرفي ستفهمه وهو طالما الوقت الحالي أقل من الوقت الذي سننتهي عنده .. استمر
ثم في النهاية عندما تنتهي الـ while
يكون الوقت الذي حددناه وصل لنهايته
لذا عند هذه النقطة استدعي الـ callback
الذي أرسلناه للدالة fun()
لنطبع في النهاية كلمة done
Callback Hell
ما هو الـ Callback Hell
؟
حسنًا المعنى الحرفي هنا هو جحيم الـ callback
وهو شيء نحاول دائما وأبدًا أن نتجنبه لأنها تجلب لنا المشقة والتعب والصعوبة في قراءة وتعديل الكود والتعقيد وتشابك الأكواد والأمر يصبح حرفيًا كالجحيم
حسنا كيف يبدو لكي نتجنبه ؟
الوعي بالمشكلة
سنستخدم دالة setTimeout
كمثال توضيحي هنا
تتذكرون دالة setTimeout
؟ ألقو نظرة عليها مجددًا
console.log('Starting');
setTimeout(function () {
console.log('This will call after 1 sec');
}, 1000);
لدينا طلب هنا وهو أن نقوم بطباعة الأعداد من 1
إلى 10
بشرط أن الفرق الزمني لطباعة الأعداد ثانية واحدة على سبيل المثال
حسنًا الأمر قد يكون سهل لنرى الأمر على رقم 1
و2
كبداية
setTimeout(function () {
console.log('No. 1');
setTimeout(function () {
console.log('No. 2');
}, 1000);
}, 1000);
هنا سيتم طباعة No. 1
بعد ثانية ثم سيتم طباعة No. 2
بعد الثانية التالية
لانه كما تلاحظ لدينا nested setTimeout function
بمعنى أن لدينا دالة setTimeout
داخل setTimeout
دالة داخل دالة
لأننا كما قلنا نحتاج أن يكون الفارق الزمني واحد ثانية بين كل رقم
لهذا جعلنا دالة setTimeout
الثانية بداخل الدالة الأولى
بحيث عند انتهاء الدالة الأولى تبدأ دالة setTimeout
الثانية بالعمل وتنتظر ثانية أخرى
الآن لكي نطبع No. 3
بعد No. 2
نحتاج لأن نجعل دالة الـ setTimeout
الخاصة بالرقم 3
تبدأ داخل دالة الـ setTimeout
الخاصة بارقم 2
بهذا الشكل
setTimeout(function () {
console.log('No. 1');
setTimeout(function () {
console.log('No. 2');
setTimeout(function () {
console.log('No. 3');
}, 1000);
}, 1000);
}, 1000);
حسنًا كيف سيكون شكل الكود عندما نصل للرقم 10
؟ دعونا نرى
setTimeout(function () {
console.log('No. 1');
setTimeout(function () {
console.log('No. 2');
setTimeout(function () {
console.log('No. 3');
setTimeout(function () {
console.log('No. 4');
setTimeout(function () {
console.log('No. 5');
setTimeout(function () {
console.log('No. 6');
setTimeout(function () {
console.log('No. 7');
setTimeout(function () {
console.log('No. 8');
setTimeout(function () {
console.log('No. 9');
setTimeout(function () {
console.log('No. 10');
}, 1000);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
}, 1000);
أظنك بدأت تخاف قليلًا الآن!؟
هذا يا صديقي ما يسمى بالـ Callback Hell
هل ترى هذا الفراغ الشاسع في ناحية اليسار الذي يشبه المثلث ؟
إن رأيته فأنت ترسم طريقك نحو جحيم الـ callback
وعليك أن تتوقف وتفكر بأسلوب آخر لكتابة الكود
ما مدى الخطورة ؟
المثال السابق كان مجرد كود يقوم بطباعة الأعداد من 1
لـ 10
فكر في نفس هذا الشكل لكن ما كود معقد أكثر
فمثلًا
- تقوم بجلب بيانات من
API
- ثم تستدعي
callback
لتقوم بعمل عمليات معينة على البيانات - ثم تستدعي
callback
آخر يقوم بحفظ البيانات في ملف ما - ثم استنادًا لبيانات التي حفظتها هناك
callback
آخر يقوم بجلب بيانات أخرى منAPI
آخر
فكر بالمثال السابق مع الحسبان أن كل دوال الـ callback
معتمدة على بعضها البعض وتستغرق وقتًا وعند الانتهاء من واحدة يتم استدعاء callback
ليقوم بمهام أخرى اعتمادًا على السابق، وهكذا
مع تعقيد الكود أكثر كيف سيبدو الـ callback hell
هنا ؟ سيكون جحيمًا حقيقيًا لك، فقط تخيل !
هل استوعبت المشكلة الآن !؟
كيف نحل المشكلة إذًا ؟
الحل ببساطة بتجنب الأمر
لأكون صريحًا لا يوجد حل وحيد ينفع لجميع الحالات
الحل الأبسط هو أن تقسم الدوال وتغير طريقة عمل الكود
ملحوظة
: يوجد أكثر من حل، وكل حل يختلف من كود لكود ومن شخص لشخص بحسب الحالة
أنا هنا غرضي توضيح الفكرة العامة باستعراض بعض الحلول للمشكلة لا أكثر
فعلى سبيل المثال، لنحاول في الكود الذي يطبع الأعداد من 1
لـ 10
تجنب الـ callback hell
المشكلة بسيطة جدًا ومباشرة لأنك تعرف ما تريده وتعرف الفترة الزمنية
تعرف أنهم 10
أعداد الفاصل الزمني ما بينهم واحد ثانية
لذا يمكنك عمل for loop
من 1
لـ 10
ثم تطبع العدد الأول بعد ثانية والعدد التاني بعد ثانيتين والعدد الثالث بعد ثلاث ثواني ... وهكذا
function logMessageAfter(message, ms) {
setTimeout(function () {
console.log(message);
}, ms);
}
for (let i = 1; i <= 10; i += 1) {
logMessageAfter(`No. ${i}`, i * 1000); // i * 1000 ms = i * 1 sec
}
أنشأنا دالة logMessageAfter
تاخذ الرسالة والمدة الزمية
داخلها يوجد setTimeout
تطبع الرسالة بعد المدة الزمنية
ثم لدينا loop
تقوم باستدعاء الدالة في كل مرة مع زيادة المدة الزمنية في كل لفة ثانية واحدة i * 1000
في اللفة الاولى ستكون ثانية واللفة التالية ستكون ثانيتين وهكذا لان الـ i
تزيد قيمتها في كل مرة
لكن بالطبع حل المثال السابق كان بسيط ومباشر لأنك تقوم بعملية تكرارية
لكن في الحياة الواقعية والمشاريع الحقيقية يكون الأمر أعقد بكثير
المثال السابق كان غرضه التوضيح من أجل الشرح لا أكثر
الشكل الشائع للـ callback hell
سأعرض مثالًا يتكرر شكله كثيرًا في الـ callback hell
وهو أن كل دالة callback
تكون معتمدة على ناتج دالة الـ callback
السابقة لها بشكل أساسي
لنستعرض مثالًا أخيرًا نختم به المقالة
لدينا مجموعة من الأرقام في أراي تدعى numbers
let numbers = [1, 2, 3, 4, 5];
ولدينا دالة تدعى add
تقوم بأخذ arr
و value
و fun
function add(arr, value, fun) {
let newArr = [...arr];
for (let i = 0; i < arr.length; i += 1) {
newArr[i] += value; // add by value to each element
}
fun(newArr); // call callback function with newArr argument
}
كل ما نقوم به هى أننا ننشيء متغير جديد يدعى newArr
يكون نسخة للـ arr
let newArr = [...arr];
لكي لا نعدل على قيم الأراي الأصلية، ثم نمر على كل عناصر الـ newArr
ونجمع عليهم قيمة الـ value
هكذا newArr[i] += value;
ثم نستدعى دالة الـ callback
وهي الـ fun(newArr)
ونرسل لها الأراي الجديدة بعد التعديل
لنرى كيف نستدعي ونستخدم دالة الـ add
add(numbers, 10, function (newArr) {
console.log(newArr); // OUTPUT: [11, 12, 13, 14, 15];
});
console.log(numbers); // OUTPUT: [1, 2, 3, 4, 5]
قمنا بإرسال numbers
و 10
بالتالي أولًا ستُنشيء الدالة نسخة عن الـ numbers
وهى newArr
ثم ستمر الدالة على كل عناصر الـ newArr
وتضيف 10
عليهم
ثم في النهاية سيتم استدعاء الـ callback
التي أرسلناها والتي تقوم باستقبال الـ newArr
ثم ستقوم فقط بطباعته
طبعًا قيمة الـ numbers
الأصلية لم تتغير لاننا نسخناها وتعاملنا مع النسخة وليس مع الأصل
حسنًا لنعلوا بالمستوى قليلًا ونقول أننا بعد ما جمعنا 10
على كل عنصر نريد أن نضربهم في 2
ولنفترض أن هناك دالة تشبه add
لكن تدعى mul
تقوم بعملية الضرب بدلًا من الجمع
function mal(arr, value, fun) {
let newArr = [...arr];
for (let i = 0; i < arr.length; i += 1) {
newArr[i] *= value; // multiple by value to each element
}
fun(newArr); // call callback function with newArr argument
}
بالتالي إن اردنا جمع 10
على العناصر ثم ضربهم في 2
فسيكون الكود هكذا
add(numbers, 10, function (newArr) {
mul(newArr, 2, function (newArr2) {
console.log(newArr2); // OUTPUT: [22, 24, 26, 28, 30];
});
});
console.log(numbers); // OUTPUT: [1, 2, 3, 4, 5]
هكذا بعد ما قمنا بجمع 10
على العناصر أخذنا newArr
في الـcallback
ثم قمنا باستدعاء دالة mul
لكي نضرب كل عناصرها في 2
كما اتفقنا
وmul
عندما تنتهي ستستدعي الـ callback
خاصتنا والتي ستستقبلnewArr2
التي ستكون الأراي النهائية بعد عملية الضرب ثم طبعناها كما ترى
حسنًا لنعلوا بالمستوى مجددًا ونقول أننا نريد عمل عمليات متعددة مثل الجمع والضرب والطرح والقسمة ولنفترض أننا لدينا دالة sub
للطرح ودالة div
للقسمة
بنفس فكرة الدوال السابقة هكذا
function sub(arr, value, fun) {
let newArr = [...arr];
for (let i = 0; i < arr.length; i += 1) {
newArr[i] -= value; // subtract by value to each element
}
fun(newArr); // call callback function with newArr argument
}
function div(arr, value, fun) {
let newArr = [...arr];
for (let i = 0; i < arr.length; i += 1) {
newArr[i] /= value; // divide by value to each element
}
fun(newArr); // call callback function with newArr argument
}
الآن نريد عمل سلسلة من العمليات على الأراي كالآتي، جمع 10
عليهم ثم ضربهم في 2
ثم طرح 5
منهم ثم قسمهم على 2
ثم جمع عليهم 5
ثم ضربهم في 4
add(numbers, 10, function (newArr) {
mul(newArr, 2, function (newArr2) {
sub(newArr2, 5, function (newArr3) {
div(newArr3, 2, function (newArr4) {
add(newArr4, 5, function (newArr5) {
mul(newArr5, 4, function (finalArr) {
console.log(finalArr);
});
});
});
});
});
});
أريدك أن ترحب مجددًا بصديقنا العزيز الأستاذ Callback Hell
!!
هذا هو الشكل الشائع له وهو أن كل callback
يعتمد بشكل أساسي على الـ callback
الذي قبله
مناقشة الشكل الشائع للـ callback hell
هل يمكنك القاء نظرة على الكود السابق وتستخرج منه العيوب ؟
ستجد أنه صعب القراءة فلو أحضرت صديقك المبرمج وقلت له أن يفهم الكود فلن يفهم الكود إلا بصعوبة شديدة
وأنت بنفسك إن رجعت للكود بعد ساعة فستجد أنك ارتبكت ولن تفهمه بسرعة
أيضًا بالتالي مع صعوبة الفهم فلن تستطيع تعديل الكود بسهولة
لانه كما قلت صعب القراءة والتتبع وكل جزء يعتمد على الذي قبله
بالتالي إن عدلت جزء فستضطر لتعديل العديد من الأجزاء هنا وهناك
مع العلم أن مثالنا صغير غرضه التوضيع والتبسيط، فتخيل مع كود حقيقي في مشروع واقعي سيكون معقد ومتشابك وأي تعديل ستضطر لكتابة الكود من البداية في أغلب الأوقات
كما قلت لا يوجد حل وحيد ينفع لجميع الحالات
بمعنى أن الكود السابق قد نحله بالعديد من الطرق
لكن إن أحضرت كودًا آخر به callback hell
فسنحله بطرق أخرى مختلفة تمامًا
let newNumbers = [];
add(numbers, 10, function (newArr) {
newNumbers = newArr;
});
mul(numbers, 2, function (newArr) {
newNumbers = newArr;
});
sub(numbers, 5, function (newArr) {
newNumbers = newArr;
});
div(numbers, 2, function (newArr) {
newNumbers = newArr;
});
add(numbers, 5, function (newArr) {
newNumbers = newArr;
});
mul(numbers, 4, function (newArr) {
newNumbers = newArr;
});
console.log(newNumbers); // OUTPUT: [4, 8, 12, 16, 20]
console.log(numbers); // OUTPUT: [1, 2, 3, 4, 5]
هذا أحد أبسط الحلول للمثال السابق، تخلصنا من مشكلة الـ callback hell
وهذا جعل الكود افضل قليلًا وليس مثاليًا لكن أفضل
أنا هنا أود توضيح الفكرة وليس اعطاء حل ثابت، لانه لا يوجد حل ثابت هنا
لا يوجد حل ثابت يمكنني أن اخبرك به سوى أن تحاول أن تتجنب الخوض في الـ callback hell
على قد استطاعتك بسبب عيوبه
الـ promise والـ async/await
في النهاية لدينا مفهومان ظهراه مؤخرًا وهما الـ promise
والـ async/await
الذي يجمعهما فكرة العمليات الغير متزامنة Asynchronous
وتسببا في حل مشاكل متنوعة من ضمنها الـ callback hell
لكن في مقالات منفصلة سنتكلم على كل واحد منهما بالتفصيل إن شاء الله