ماهي الوعود في الجافاسكربت
الوعود في لغة الجافاسكربت, ماهي, اهميتها و طريقة الاستخدام
السلام عليكم و رحمة الله و بركاته
جميع الامثلة ستكون ذات علاقة بالـ Node.JS و هذا الدرس سيكون مقدمة لدرس آخر
في السابق تحدثت عن مفهوم الـcallback ، إيجابياته، سلبياته و تطرقت لـ السلبية الأكبر وهي جحيم الكولباك callback hell وهو عبارة عن عدد من الدوال المتداخلة في بعضها ، تشكل هرم من الأقواس وتعتمد كل منها على مخرجات الاخرى حيث يصعب بعد ذلك تطوير هذه الجزئية من الكود لكثرة التداخلات وكذلك تصعب عملية حصر الاخطاء، وهو بالفعل ما يسمى بـ جحيم الكولباك (كما اسلفنا) و في الصورة ادناه مثال على ذلك.
لهذا .. وٌجِد بما يٌسمى بالـ وعود "Promises" حيث يعمل الوعد على تمثيل الناتج لأي عملية / دالة asynchronous تريدها بطريقة اكثر (حضارية) ان صح التعبير، و يكون الوعد في ثلاث حالات three states:
-
pending : الحالة الاولية للوعد، في حالة التأهب لاستقبال الوعد.
-
resolved أو fulfilled : تم إيفاء الوعد وانتهى عند هذا الحد.
-
rejected : تم رفض الوعد مع السبب وانتهى عند هذا الحد.
التطبيق النظري للوعود
للوعود عدة أشكال/ميثودز syntax واهمها هي:
function GetData() {
return new Promise(function (resolve, reject) {
if(/* condition true and satisfied */)
resolve (/* results */)
else
reject (/* reason/s why it has been rejected */)
});
}
كما نلاحظ في المثال أعلاه، اولا قمنا بإنشاء وعد new Promise و من ثم الدالة التي تأخذ متيغيرين إثنين resolve في حالة ايفاء الوعد و reject في حالة الرفض ( وقد تكون في حالة وجود مشكلة error ).
التطبيق العملي للوعود
في جميع التطبيقات على ارض الواقع, يقع المبرمج في امور معقدة قد تتطلب منه استخدام هذا النوع من الدوال, ومثالا على ذلك - الحالة 1:
- قاعدة بيانات لمدرسة تحتوي على عدة جداول منها 3 جداول رئيسية ( التي نحتاجها فقط).
- جدول يحتوي على معلومات الطلبة (ID, FullName, AcademicNumber, DOB).
- جدول آخر يحتوي على المواد التي يدرسها الطالب (ID, CourseID, StudentID).
- جدول ثالث يحتوي على (ID, CourseCode, CourseLevel, CourseHours)
مع الأخذ بالاعتبار ان الـ ID في كل جدول عيارة عن Primary Key Auto Inc, ماذا لو ادرت ان تعرف *اسماء المواد التي يدرسها الطالب و بالأسماء اعني CourseCode* ؟ حتما ستقع في جحيم الكولباك , لانك اولا تختار ID الطالب وتتاكد من وجوده ام عدمه, ان وجد انتقلت للمرحلة الثانية وهي اختيار الـ CourseID مع وجود StudentID و التأكد من وجودة ام عدمة و اخيرا اخيار الـ CourseCode من الجدول الاخير و التاكد من وجوده ام عدمة.
المشكلة في المثال ليست "وجود ام عدم" النتيجة, بل في الدوال التي ستكون مترابطة في بعضها بما لا يقل عن 3 بالتالي ستصعب عملية حصار الأخطاء او التعامل ككل مع المسألة , وان تعقدت اكثر ( الله يستر).
وهنا يأتي دور الـ Promises في تفكيك هذه العملية الى اجزاء, دوال بسيطة, ونبدأ بكتابة دوال الـ Promises
function CourseCodesByStudentID(...value) {
return new Promise(function (resolve, reject) {
var sql_query = 'SELECT `ID` As std_id FROM `students` WHERE `ID` = ?';
var sql_parameters = [value[0]];
db.connection.query(sql_query, sql_parameters, function (err, results, fields) {
if (err) {
reject(err);
} else {
if (results.length) {
resolve(results);
} else {
reject('the student cannot be found');
}
}
});
});
}
function StudentCourses(...value) {
return new Promise(function (resolve, reject) {
var sql_query = 'SELECT `CourseID` FROM `StdCourses` WHERE `StudentID` = ?';
var sql_parameters = [value[0]];
db.connection.query(sql_query, sql_parameters, function (err, results, fields) {
if (err) {
reject(err);
} else {
if (results.length) {
// You can use Array.prototype.map() Or Array.prototype.forEach()
let coursesArray = [];
for (let i = 0; i < results.length; i++) {
coursesArray.push(results[i].CourseID);
}
resolve(coursesArray);
} else {
reject('no courses found for this student');
}
}
});
});
}
function CoursesCodes(...value) {
return new Promise(function (resolve, reject) {
var sql_query = 'SELECT `CourseCode` FROM `Courses` WHERE `ID` IN (?)';
var sql_parameters = [value[0]];
db.connection.query(sql_query, sql_parameters, function (err, results, fields) {
if (err) {
reject(err);
} else {
if (results.length) {
resolve(results);
} else {
reject('no courses found');
}
}
});
});
}
وكما هو موضح مخرجات كل دالة في الأسفل, في هذه المرحلة جميل الوعود في حالة pending في انتظار التفعيل او البدأ في عملية الرفض ام القبول وتكون كالتالي:
- للحصول على مخرجات وعد واحد
/* To get a resutls from one promise */
// let's assume that the StudentID is 20098821
CourseCodesByStudentID(20098821).then(function(results1){
/*
OUTPUT
[
{
"std_id": 1234
}
]
*/
let res_from_promise_1 = results1[0].std_id;
console.log(res_from_promise_1);
})
.catch(function(err){
console.log(err);
});
- للحصول على مخرجات سلسلة من الوعود
/* Chain of promises
Don't forget, we already assumed that the StudentID is 20098821, so this is how we pass it
*/
CourseCodesByStudentID(20098821).then(function(results1){
/*
OUTPUT
[
{
"std_id": 1234
}
]
*/
let res_from_promise_1 = results1[0].std_id;
// now we pass it to the second promise in this way
return StudentCourses(res_from_promise_1); // fire the second Promise
})
.then(function(resutls2){
/*
OUTPUT
res_from_promise_2 == results2
[
105,
325,
987,
536
]
*/
let res_from_promise_2 = resutls2 // not necessary to declare a variable here, but I'm doing it just as an example that u might use it as u wish
return CoursesCodes(res_from_promise_2); // fire the third Promise
})
.then(function (finalResutls) {
console.log(finalResutls);
/*
OUTPUT
finalResutls
[
"CourseCode":"ENB101",
"CourseCode": "FLK106",
"CourseCode": "KLO204",
"CourseCode": "ARB101",
]
*/
})
.catch(function(err){
console.log(err);
});
و في الحالة الثانية للأستفادة اكثر من الـ Promises , * ماذا لو كان لديك تطبيق اخباري تزودة بالمعلومات بشكل متفرق, اي لا يعتمد على سيرفر خاص بك و لايعتمد على سيرفر واحد بل عدة * , ستحتاج في هذه الحالة الى Method تدعى race وهي اسم على مسمى حيث سيتم ايقاف الوعد عند اول resolve
var Source1 = new Promise(function (resolve, reject) {
var data_to_stringfy = {
apiKey: "XKJS09sJFsg",
path: "local"
}
var url = 'https://bahrain.news.com/api';
var data = JSON.stringify(data_to_stringfy);
request.post({
headers: {
'Content-Type': 'application/json'
},
url: url,
body: data
}, function (error, response, body) {
if (error) reject(error);
else {
let parsedBody = JSON.parse(body);
resolve(parsedBody);
}
});
});
var Source2 = new Promise(function (resolve, reject) {
var url = "http://mysite.com/json";
request.get({
url: url
}, function (error, response, body) {
if (error) reject(error);
else {
resolve(body);
}
});
});
Promise.race([Source1, Source2])
.then(function(finalResult) {
// Both promises might be resolved but the results will be the first promise to finish, could be Source1 or Source2 it doesn't matter
console.log(finalResult);
})
.catch(function(error){
console.log(error);
});
الحالة الثالثة والاخير التي سنعرضها في هذا الدرس هي * اي امر يتطلب عدة خطوات معينة لإستيفائة كـ عملية اطلاق الصاروخ حيث يجب ان تتم العملية بخطوات محددة و يجب ان تكون جميعها صحيحة " توفي الوعد" * ( بالعربي اما كلشي يمشي سليم ولا .. تجيب العيد ) , ستحتاج في هذه الحالة الى Method تدعى All يتم ايقاف الوعد فيها عند اول Reject على عكس Race
var promise1 = Promise.resolve(3);
var promise2 = 42;
var promise3 = new Promise(function(resolve, reject) {
setTimeout(resolve, 100, 'foo');
});
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
});
// expected output: Array [3, 42, "foo"]
{ الحمد لله أقصى مبلغ الحمد .. والشكر لله من قبل ومن بعد }
انتهى .. للأسئلة في التعليقات
لايوجد لديك حساب في عالم البرمجة؟
تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !