الـ Promise، وعود الجافاسكريبت !
السلام عليكم ورحمة الله وبركاته
الفهرس
- المقدمة
- ما هو الـ Promise
- الوعي بالمشكلة
- استخدام الـ Promise
- حالات الـ Promise
- مثال توضيحي لاستخدام الـ Promise
- التعامل مع الـ reject والـ catch
- مشكلة الـ callback hell مع الـ promise
- حل مشكلة الـ callback hell في الـ promise
- Promises chaining
- الـ catch في الـ promises chaining
- .finally(...)
- ما هي الـ Promise.resolve(...) والـ Promise.reject(...)
- تحويل callback إلى promise
- مثال عملي باستخدام دالة fetch
المقدمة
في هذه المقالة سنتحدث عن الـ promise
لما هي موجودة وما فائدتها وكيف نتعامل معها وكيف ننشيء promise
خاص بنا
ما هو الـ Promise
هو object
يحتوي على دوال وخصائص متنوعة يتعامل مع البيانات التي تستغرق وقتًا
المعنى الحرفي له هو وعد أي انه يعدك بالحصول على نتيجة
أحيانًا يكون هناك عمليات تحتاج لوقت لكي تنتهي فمثلًا جلب بيانات من API
أو قراءة ملف فهذه عمليات قد تستغرق وقتًا
فالـ promise
هنا يستطيع تولي هذه الأمور وعندما ينتهي من تنفيذ العملية سيخبرك الـ promise
أنه انتهى
الوعي بالمشكلة
تذكر أنك هنا أنت لا تعرف ولن تستطيع أن تعرف الوقت الذي ستنتهي فيه العملية، لأنه ليس وقتًا ثابتًا فيمكن أن يستغرق جلب البيانات من API
ثانية أو ثانيتين أو حتى 5
ثواني
لا يوجد وقت ثابت لانه يعتمد على عوامل متغيرة كثيرة
هل هذه مشكلة ؟
حسنًا ان كنت تريد تنفيذ كود ما مباشرةً بعد انتهاء العملية التي ستستغرق وقتً، فكيف ستعرف متى ستنتهى ؟
فمثلًا لدينا صفحة عندما تفتحها تقوم بجلب البيانات من API
ثم تعرضها في الصفحة
كيف ستعرف متى سيتم جلب البيانات لكي تعرض البيانات في الصفحة ؟
وهل سيتوقف كامل الموقع عن العمل لحين انتهاء هذه العمليات ؟
هنا أتى الـ promise
الذي يستطيع أخذ الكود الذي سيستغرق وقتًا
ثم عندما ينتهي من تنفيذ العملية سيخبرنا الـ promise
أنه انتهى
ويمكنك حينها تنفيذ ما تريد ويظل الموقع يعمل بسلاسة دون أن يتأثر بهذه العمليات
استخدام الـ Promise
let ourPromise = new Promise(function (resolve, reject) {});
هنا أنشأنا promise
وكما تلاحظ فأنه يأخذ callback function
كـ argument
كما ترى هنا function (resolve, reject) {}
وهذا الـ callback
كما ترى يستقبل متغيرين resolve
و reject
، يمكنك بالطبع تسميتهما بأي اسم لكن يجب الترتيب مهم طبعًا
ويتم تخزين كل هذا في متغير يدعى ourPromise
حسنًا ما هما الـ resolve
والـ reject
بالتحديد ؟
هما في الحقيقة دوال الأولى هي resolve
نستدعيها عندما ينجح الـ promise
الخاص بنا ونستدعي reject
عندما يحدث أي خطأ
بهذا الشكل
let ourPromise = new Promise(function (resolve, reject) {
resolve('success'); // call resolve if there is no errors
reject('there is an error'); // call reject if there is an errors
});
ملحوظة
: سنرى فيما بعد ما استخدامات عملية للـresolve
وreject
فيما بعد في الأمثلة التالية
نستدعي resolve
عندما تنجح العملية ونستطيع إرسال أي معلومات معها سواء جملة أو object
أو أي بيانات تريد أن ترسلها، هنا نحن فقط أرسلنا جملة تقول success
وإذا حدث أي خطأ فسنستدعي reject
ونرسل أي شيء أيضًا بنفس الفكرة
دعونا نرى مثالا للتوضيح
let ourPromise = new Promise(function (resolve, reject) {
let isSuccess;
// code that takes long time ...
// read file or fetch API
isSuccess = true; // determinate if it success or fail
if (isSuccess) resolve('success');
else reject('fail');
});
حسنًا هذا كود تخيُلي للأمر
لدينا متغير يدعى isSuccess
يحدد ما إذا نجحت العملية أو حدث خطأ ما
ثم لدينا كود يستغرق وقتًا طويلًا كما قلنا أي كان الكود
ثم لدينا شرط يفحص الـ isSuccess
ويرى إذا لم يحدث خطأ فاستدعي resolve
وإذا حدث خطأ فاستدعي reject
حالات الـ Promise
قبل أن نكمل عليك أن تعرف أن هناك ثلاث حالات فقط للـ promise
وهم:
pending
: تعني أن العملية لم تنتهي بعدfulfilled
: تعني أن العملية قد انتهت بنجاحrejected
: تعني أنه حصل خطأ ما اثناء العملية
مثال توضيحي لاستخدام الـ Promise
لنقول أننا نريد جمع رقمين ولنتظاهر أنها عملية تأخذ وقتًا طويلًا
let ourPromise = new Promise(function (resolve, reject) {
// this code takes 3 sec
setTimeout(function () {
let x = 5;
let y = 7;
// reject negative numbers
if (x < 0 || y < 0) reject('Negative numbers are not accepted');
else {
let sum = x + y;
if (sum > 0) resolve(sum);
}
}, 3000); // 3000 ms = 3 second
});
استعملنا setTimeout
لنحاكي أن هذه العملية ستستغرق 3
ثواني كمثال
الآن ستلاحظ هنا أننا وضعنا شرط أننا لا نقبل الأعداد السالبة
إذا وجدنا أي عدد سالب فسنستدعي reject
ونرسل رسالة توضحية بأننا لا نقبا الأعداد السالبة
واذا كانا الرقمين موجبين فأننا نجمعهما ثم نرسل ناتج الجمع مع resolve
عندما ينتهي
تذكر أن الـ promise
نخزنه في متغير يدعى ourPromise
كما تلاحظ
الآن هل نستطيع الحصول على الناتج عندما نطبع المتغير ourPromise
؟
دعونا نرى
console.log(ourPromise);
الناتج:
Promise {<pending>}
سنتلاحظ انه طبع لك object
من نوع Promise
يحتوي على حالة pending
أي ان العملية لم تنتهي بعد، حسنًا كيف نعرف متى ستنتهي ؟
هنا يأتي دور دالة داخل الـ Promise
تدعى then
يتم تفعيلها بعد اكتمال العملية
ourPromise.then(function (result) {
console.log(result); // OUTPUT: 500500
});
دالة then
تستقبل callback
وهذا الـ callback
معه متغير يحتوي على الناتج الذي ارسلناه في الـ resolve
وتذكر أننا أرسلنا sum
داخل الـ resolve
بالتالي قيمة الـ result
ستكون قيمة الـ sum
بالتالي هنا كما قلنا دالة then
سيتم تفعيلها عندما ينتهي الـ promise
من العملية
هكذا عندما يكون هناك عملية تعتمد على عملية أخرى وهذه العملية الأخرى أنت لا تعرف متى ستنتهي، فيمكنك حينها وضع العملية الأولى داخل
promise
ثم تجعل العملية الثانية تنتظر الناتج داخل الـthen
let ourPromise = new Promise(function (resolve, reject) {
resolve('Success');
});
console.log(ourPromise); // OUTPUT: Promise { 'Success' }
إذا كان ourPromise
يقوم بعمل resolve
لكلمة Success
وقمنا بطباعة الـ ourPromise
فستجد أنه يرجع لك promise
يحتوي على كلمة Success
والـ ourPromise
يكون fulfilled
في هذه الحالة لاننا قمنا بعمل resolve
بشكل مباشر
ولكي نحصل على الجملة التي ارسلناها مع الـ resolve
يجب أن نستخدم then
كما قلنا
ourPromise.then(function (result) {
console.log(result); // OUTPUT: Success
});
التعامل مع الـ reject والـ catch
الآن فهمنا أن بعد ما تنتهي العملية تقوم بعمل resolve
ثم نحن نعلم متى ستنتهي العملية عن طريق دالة then
الآن ماذا يحدث عندما يحدث reject في الـ promise ؟
لنحاول جعل المتغيرات ونرى ماذا سيحدث
let ourPromise = new Promise(function (resolve, reject) {
// this code takes 3 sec
setTimeout(function () {
let x = -5;
let y = -7;
// reject negative numbers
if (x < 0 || y < 0) reject('Negative numbers are not accepted');
else {
let sum = x + y;
if (sum > 0) resolve(sum);
}
}, 3000); // 3000 ms = 3 second
});
هنا الـ x
والـ y
اصبحها -5
و -7
ثم انتظرنا الـ then
بشكل الاعتيادي
ourPromise.then(function (result) {
console.log(result); // ERROR!!
});
ستجد أمامك رسالة حب جميلة هنا، مفادها
[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "Negative numbers are not accepted".] {
code: 'ERR_UNHANDLED_REJECTION'
}
الرسالة تقول أنه لم يجد شيء يتعامل مع الـ reject
أي لم يجد .catch()
ما معنى هذا ؟ ببساطة الـ then
مصممة لتتعامل مع حالات الـ resolve
فقط وليس مع الـ reject
لذا يجب أن نتعامل مع الـ reject
بشكل مستقل وهنا لدينا حلين أول طريقتين كما قلنا
الحل الأول أن نستعمل دالة تدعى catch
وهو ببساطة أي reject
يحدث يذهب إلى دالة الـ catch
ourPromise
.then(function (result) {
console.log(result);
})
.catch(function (error) {
console.log(error);
});
هنا اذا حدث أي result
سيتم تفعيل then
واذا حدث أي reject
سيتم تفعيل catch
في حالتنا هذه سيتم تفعيل catch
الذي يستقبل callback
معه متغير يحتوي على الرسالة التي ارسلناها اثناء الـ reject
وكانت Negative numbers are not accepted
مشكلة الـ callback hell مع الـ promise
حسنًا تتذكر صديقنا العزيز callback hell
من المقالة الخاصة بالـ callback
سنواجه مجددًا هنا، لكن لا تخف لأن الـ promise
لديها طريقة جيدة لحل هذه المشكلة
لكن أولًا لنستعرض شكل المشكلة بمثال وكيف ستكون مع الـ promise
function addTwo(x, y) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(x + y);
}, 3000);
});
}
هنا لدينا دالة تدعى addTwo
تأخذ قيمتين وتجمعهم
ومثل ما اعتدنا سنقول أن عملية الجمع تستغرق 3
ثواني كمثال
لذا بسبب أنها عملية تستغرق وقتًا فأننا جعلناها داخل promise
دالة addTwo
كما تلاحظ فإنها تقوم بإرجاع هذا الـ promise
بمعنى أنه عندما نستدعي الدالة هكذا
addTwo(5, 7); // return a promise (Promise {<pending>})
فانها سترجع لنا promise
في حالة pending
بالتالي فيجب أن نستعمل then
لكي ننتظر انتهاء العملية
addTwo(5, 7).then(function (result) {
console.log(result); // OUTPUT: 12 (after 3 second)
});
حتى الآن لا شيء جديد علينا
دالة الـ then
ستجمع الرقمين ثم ننتظر الناتج في الـ then
بعد 3
ثواني
حسنًا إذا أردنا الآن أن نجمع 10
علي الناتج result
بهذا الشكل
addTwo(5, 7).then(function (result) {
addTwo(result, 10); // add 10 to the result
});
داخل الـ then
استدعينا الدالة addTwo
مجددًا لكي نجمع 10
على الناتج كما قلنا
هنا الـ addTwo
الثانية سترجع لنا promise
لذا سنقوم بعمل then
لها
addTwo(5, 7).then(function (result) {
addTwo(result, 10).then(function (result2) {
console.log(result2); // OUTPUT: 22 (after 6 second)
});
});
حسنًا أظنك بدأت تستوعب كيف ستظهر مشكلة الـ callback hell
الآن
لنكبر العمليات قليلًا لنضح عملية جمع اخرى للناتج الأخير result2
addTwo(5, 7).then(function (result) {
addTwo(result, 10).then(function (result2) {
addTwo(result2, 12).then(function (result3) {
console.log(result3); // OUTPUT: 34 (after 9 second)
});
});
});
ماذا عن وضع 5
عمليات أخرى ؟
addTwo(5, 7).then(function (result) {
addTwo(result, 10).then(function (result2) {
addTwo(result2, 12).then(function (result3) {
addTwo(result3, 11).then(function (result4) {
addTwo(result4, 20).then(function (result5) {
addTwo(result5, 32).then(function (result6) {
addTwo(result6, 18).then(function (result7) {
addTwo(result7, 99).then(function (result8) {
console.log(result8); // OUTPUT: 214 (after 24 second)
});
});
});
});
});
});
});
});
رحب معي بصديقنا المتواضع callback hell
بنفسه هنا
حسنًا الآن كيف سنتخلص منه :)
حل مشكلة الـ callback hell في الـ promise
حسنًا لنعود للبداية
addTwo(5, 7).then(function (result) {
console.log(result); // OUTPUT: 12 (after 3 second)
});
حسنًا قلنا أننا نريد أن نجمع 10
علي الناتج result
ثم استدعينا AddTwo
أخرى وربطناها بـ then
هكذا
addTwo(5, 7).then(function (result) {
addTwo(result, 10).then(function (result2) {
console.log(result2); // OUTPUT: 22 (after 6 second)
});
});
هنا بدأت المشكلة بالظهور
الحل بسيط جدًا وهو أن دالة الـ then
تملك return
ويمكننا إرجاع أي شيء داخلها
هكذا
addTwo(5, 7).then(function (result) {
return addTwo(result, 10); // this `then` return a promise (Promise {<pending>})
});
ركز معي هنا جيدًا، داخل الـ then
أرجعنا دالة الـ addTwo
أو بمعنى آخر لقد أرجعنا promise
حسنًا دالة الـ then
عندما ترجع لنا promise
فيمكننا حينها ربط then
أخرى بها
ما معنى هذا ؟ أي يمكننا استدعاء then
اخرى من خلال الـ then
الأولى
هكذا
addTwo(5, 7)
.then(function (result) {
return addTwo(result, 10);
})
.then(function (result) {
console.log(result); // OUTPUT: 22 (after 6 second)
});
حسنًا عرفنا أن الـ then
الأولى أرجعت promise
لأن دالة addTwo
هى promise
بحد ذاتها
فطالما أن الـ then
الأولى ترجع promise
فيمكننا ربط then
أخرى بهذا الـ promise
والـ then
الثانية ستستدعى عندما تنتهي العملية في الـ addTwo
الثانية التي ارجعناها
لنضيف عملية أخرى
addTwo(5, 7)
.then(function (result) {
return addTwo(result, 10);
})
.then(function (result) {
return addTwo(result, 12);
})
.then(function (result) {
console.log(result); // OUTPUT: 34 (after 9 second)
});
يمكنك أن تستوعب الآن أن كل then
ترجع promise
بالتالي نستدعي then
جديدة لتتعامل مع الـ promise
الذي رجع من الـ then
السابقة
الكود التالي سيكون كود توضيحي عام مجرد من أي دوال من أجل الشرح والتبسيط
promise_1(...)
.then(function (...) {
// this then will wait for `promise_1`
return promise_2(...);
})
.then(function (...) {
// this then will wait for `promise_2`
return promise_3(...);
})
.then(function (...) {
...
})
.then(...)
.then(...)
...
الأمر كله يبدأ بـ promise_1
ثم انتظرناه لينتهي في الـ then
الأولى
ثم قمنا بإرجاع promise_2
ثم انتظرناه لينتهي في الـ then
الثانية
ثم قمنا بإرجاع promise_3
ثم انتظرناه لينتهي في الـ then
الثالثة
ثم قمنا بإرجاع ... ثم انتظرناه لينتهي في الـ ...
ثم ... ثم ... ثم ... ثم
يمكنك أن تغنيها :)
كل then تقوم بإرجاع promise وتسلم للبعدها
دعونا نرى المثال الخاص بالـ Promises chaining
بهذه الطريقة
Promises chaining
addTwo(5, 7)
.then(function (result) {
return addTwo(result, 10);
})
.then(function (result) {
return addTwo(result, 12);
})
.then(function (result) {
return addTwo(result, 11);
})
.then(function (result) {
return addTwo(result, 20);
})
.then(function (result) {
return addTwo(result, 32);
})
.then(function (result) {
return addTwo(result, 18);
})
.then(function (result) {
return addTwo(result, 99);
})
.then(function (result) {
console.log(result); // OUTPUT: 214 (after 24 second)
});
حسنًا الآن أصبح لدينا مبنى من 8
طوابق، هذا تغير ملحوظ كما ترى
وأفضل من الـ callback
هذا الشكل الذي تراه يسمى Promises chaining
وهو اسم يناسبه
يمكنك هنا هدم طابق إن كنت لا تريده ... أقصد يمكنك هنا ازالة أي then
بسهولة دون أن تضطر إلى تعديل الباقي
إن كنت تتذكر فكان من الصعب ازالة عملية من المنتصف الـ callback hell
لانها تكون مندمجة مع الـ callback
التي فوقها والتي تحتها
حاول أن تزيل callback
من منتصف الـ callback hell وستفهم المعاناه
يمكنك أن تحفظ شكل الـ Promises chaining
هكذا
Promise
.then(...)
.then(...)
.then(...)
.then(...)
...
الـ catch في الـ promises chaining
حسنًا استدعاء دالة الـ catch
في نهاية سلسلة الـ then
تلك
بهذا الشكل
Promise
.then(...)
.then(...)
.then(...)
.catch(...) // catch any exception
والـ catch
يمكنها استقبال أي throw New Error(...)
أو return Promise.reject(...)
أو أي خطأ عموما قد يحدث في أي then
في السلسلة كـ syntax error
أو عدم تعريف متغير وهكذا من الأخطاء المختلفة
سنأخذ بعض الأمثلة
لنفترض أننا وضعنا شرط في الـ then
الثانية أن الناتج لا يجب أن يتجاوز الـ 20
على سبيل المثال
addTwo(5, 7)
.then(function (result) {
return addTwo(result, 10);
})
.then(function (result) {
if (result > 20)
return Promise.reject('Result that greater than 20 is not accepted');
return addTwo(result, 12);
})
.then(function (result) {
console.log(result);
})
.catch(function (err) {
console.log(err); // Result that greater than 20 is not accepted
});
الآن الـ then
الثانية سترجع لنا promise
معمول له reject
والـ catch
بطبيعة الحالة ستستقبل هذا الـ reject
وتتعامل معه
Promise
.then(...) // success
.then(...) // success
.then(...) // return promise.reject or throw "error message"
.then(...) // will be ignored
.then(...) // will be ignored
.then(...) // will be ignored
.catch(...) // handle that reject
عندما يحدث reject
في أي then
سيتم تجاهل كل الـ then
التالية وسيتم القفز لدالة الـ catch
مباشرةً
وكما قلنا سابقًا الـ then
مصممة لتتعامل مع حالات الـ resolve
فقط وليس مع الـ reject
وأي reject
يحدث يذهب إلى دالة الـ catch
ولو اردنا استخدام الـ throw
فالأمر سواسية
addTwo(5, 7)
.then(function (result) {
return addTwo(result, 10);
})
.then(function (result) {
if (result > 20) throw 'Result that greater than 20 is not accepted';
return addTwo(result, 12);
})
.then(function (result) {
console.log(result);
})
.catch(function (err) {
console.log(err); // Result that greater than 20 is not accepted
});
الكود هنا بالـ throw
ولا يختلف عن سابقه، فقط نرسل الرسالة التي نريدها وسيتم الذهاب إلى الـ catch
بنفس رسالة الخطأ
.finally(...)
هذه الدالة تكتب في نهاية السلسة بعد الـ .catch(...)
Promise
.then(...)
.then(...)
.then(...)
.catch(...)
.finally(...)
وتنفذ دائمًا حتى اذا حصل resolve
أو reject
للـ promise
أي تنفذ في نهاية العملية سواء نجحت أو فشلت
تستخدم غالبًا عندما تكون متصل بـ database
وتريد القيام بعملية ما
ثم بعد ما يتم تنفيذ العملية سواء نجحت أو فشلت نقوم بقطع الاتصال بقاعدة البيانات
Database.connect()
.then(function () {
return Database.query('SELECT * FROM users');
})
.then(function (result) {
console.log(result);
})
.catch(function (err) {
console.log(err);
})
.finally(function () {
Database.disconnect();
});
بالطبع هذا مثال بسيط وتخيلي ولكن يوضح الفكرة
فهنا نحن نفترض أننا لدينا Database
ونريد الاتصال بها ثم نقوم بعمل query
ثم نقوم بعمل disconnect
بعد الانتهاء من العملية سواء نجحت أو فشلت
وهذا قد يكون مفيدًا في بعض الحالات وقد تحتاجه في بعض الأحيان
لذا إذا واجهت مشكلة ما وتريد القيام بعملية ما بعد الانتهاء من الـ promise
فهذه الدالة finally
تكون مفيدة
ما هي الـ Promise.resolve(...) والـ Promise.reject(...)
نحن لديناها Promise.resolve(...)
التي تعطي دائما promise
في حالة fulfilled
Promise.reject(...)
التي تعطي دائما promise
في حالة rejected
مثال على استخدام Promise.resolve(...)
Promise.resolve('Success')
.then(function (result) {
console.log(result); // Success
})
.catch(function (error) {
console.log(error);
});
أول then
ستم تنفيذها فورًا دون أي عقبات لأنها مرتبطة بـ Promise.resolve('Success')
بشكل مباشر فطالما حصل resolve
فسيتم تنفيذ أول then
فورًا
والـ catch
سيتم تنفيذها إذا حصل أي throw
أو Promise.reject
كما وضحنا
مثال على استخدام Promise.reject(...)
Promise.reject('Error')
.then(function (result) {
console.log(result);
})
.catch(function (error) {
console.log(error); // Error
});
في حالة Promise.reject
دائما وابدًا سيتم تنفيذ الـ catch
وسيتم تجاهل كل الـ then
تحويل callback إلى promise
هذا سيكون آخر فقرة في مقالتنا البسيطة
إن شاء الله تكون استوعبت المفهوم العام للـ promise
وتطبيقاته
الآن لنفترض أننا لدينا دالة تدعى add
تقوم بأخذ أراي ورقم ما ثم تجمع هذه الرقم على جميع عناصرها
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
}
مثل الدالة في مقالة الـ callback
غالب الأمر ستجد أننا نفضل استخدام الـ promise
بدلًا من الـ callback
لذا ستجد نفسك تحول دوال الـ callback
الى دوال promise
وهذا له فائدة اخرى سنعرفها في درس الـ await
، حيث أننا نستطيع استخدام الـ await
مع الـ promise
لكن هذا سنشرحه في مقالة اخرى
دالة الـ add
حاليًا نستدعيها بهذا الشكل
let number = [1, 2, 3, 4, 5];
add(number, 5, function (result) {
console.log(result); // OUTPUT: [6, 7, 8, 9, 10]
});
التحويل من callback
إلى promise
أسهل ما يمكن
أولًا نقوم بعمل دالة أخرى والتي ستكون نسخة الـ promise
function addPromise(arr, value) {
return new Promise(function (resolve, reject) {});
}
الدالة ستستقبل نفس المتغيرات التي تستقبلها دالة الـ callback
الـ arr
والـ value
وستقوم بإرجاع promise
لأن هذا ما نريده
الفكرة هي استدعاء الدالة الأصلية add
داخل هذا الـ promise
ثم عمل resolve
لناتج الـ callback
function addPromise(arr, value) {
return new Promise(function (resolve, reject) {
add(arr, value, function (newArr) {
resolve(newArr); // send the newArr in the resolve
});
});
}
كل ما فعلناه هو عندما يتم استدعاء الـ callback
الخاص بدالة الـ add
الأصلية قمنا بعمل resolve
للناتج
هكذا نستطيع استخدامها كـ promise
وننتظر الناتج في الـ then
addPromise(number, 5).then(function (result) {
console.log(result); // OUTPUT: [6, 7, 8, 9, 10]
});
طبعًا نستطيع عمل reject
داخل نسخة الـ promise
بأي شرط
مثلًا لا نريد أن نجمع أرقامًا سالبة
function addPromise(arr, value) {
return new Promise(function (resolve, reject) {
add(arr, value, function (newArr) {
if (value < 0) reject("Can't add negative number.");
resolve(newArr); // send the newArr in the resolve
});
});
}
حينها نستخدم الـ catch
ليتعامل مع الأمر
addPromise(number, 5)
.then(function (result) {
console.log(result); // OUTPUT: [6, 7, 8, 9, 10]
})
.catch(function (err) {
console.log(err); // ERROR: Can't add negative number.
});
مثال عملي باستخدام دالة fetch
في الجافاسكريبت
لدينا دالة الـ fetch
التي تستخدم للتعامل مع الـ API
واستخراج البيانات من الملفات وغيرها
fetch(URL); // return a promise
فالـ fetch
بسيطة جدًا فقط تأخذ المسار وترج لك promise
بالتالي ان حصل resolve
للناتج فسيتم تنفيذ then
وان حصل اي reject
فسيتم تنفيذ الـ catch
نحن من أجل الشرح لنستخدمها لاستخراج البيانات من ملف JSON
لذا سننشيء ملف JSON
ولنسميه file.json
دعونا نأخذ مثال لبيانات منتج معين وليكن نوع من انواع الهواتف الذكية العالمية
نفتح ملف file.json
ثم نكتب البيانات بهذا الشكل
{
"name": "Redmi 9",
"brand": "Xiaomi",
"colors-available": ["gray", "violet", "green"],
"ram": 4,
"space": 64,
"fast-charging": true,
"water-resistant": false
}
أفترض أنك تعرف ما هو الـ
JSON
من الأساس, وإن كنت لا تعرف فأرجوا أن تبحث قليلًا وتأخذ فكرة عنه، إنه وسيلة متعارف عليها لتخزين البيانات، وأظن أنك بمجرد قراءتك للملف فهمته
دعونا نبدأ بكود بسيط لاستخراج البيانات من ملف file.json
fetch('./file.json').then(function (res) {
console.log(res); // response is a Response object
});
هنا قيمة الـ res
الآن ليست البيانات التي بداخل ملف الـ file.json
قيمة الـ res
هي Response object
ما هو الـ Response object ؟
الأمر ببساطة لأن طبيعة دالة الـ fetch
هي التعامل مع API
وروابط مختلفة خارجية
وهذه الروابط قد تجلب لنا بيانات متنوعة ومختلفة مثلًا نص او json أو حتى صورًا
لذا لدينا object
من الـ Response
يكون وسيطًا ليعطينا معلومات عن ما تم احضاره وكيف نتعامل معه اذا كان نصًا او صورة على سبيل المثال
داخل object
الـ Response
الذي في حالتنا اسميناه res
لدينا دوال و متغيرات متنوعة منها
headers
: يحتوي على كل الـHeaders
التي بداخل هذا الـResponse
status
: متغير يحتوي على رمز الحالة الخاص بالـResponse
ok
: قيمته تكونboolean
إذا كانت رمز حالة الـResponse
ما بين200
و299
قيمته تكونtrue
عدا ذلك تكونfalse
json()
: تقوم بتحويل البيانات التي في الـResponse
إذا كانتJSON
إلىJavascript Object
وترجعه فيpromise
blob()
: تقوم بتحويل البيانات التي في الـResponse
إذا كانت صورة إلىJavascript Object
وترجعه فيpromise
ملحوظة
: كل هذا الكلام لا يمد بالـPromise
بصلة لكننا نقوله لان دالة الـfetch
تستخدم هذا الـResponse object
لوصف ومعرفة معلومات عن حالة هذا الـresponse
لذا اضطرينا إلى شرح بعض معلومات عن الـResponse object
يمكنك قراءة المزيد من المعلومات عن الـ Response object
من هنا
تحويل الـ JSON إلى Javascript Object
لا نريد أن نخوض في الأمر كثيرًا، لكن ما يهمنا هنا هي دالة res.json()
وهي ترجع لنا promise
تقوم بتحويل البيانات من JSON
إلى Javascript object
fetch('./file.json').then(function (res) {
return res.json(); // return a resolved promise that parsing json to javascript object.
});
دالة الـ then
الآن سترجع res.json()
الذي بدوره سيرجع لنا promise
معمول له resolve
بالبيانات التي بداخل الـ Response object
بعد تحويلها إلى javascript object
طالما الـ then
الأولى أرجعت لما promise
فسنحتاج لـ then
أخرى
fetch('./file.json')
.then(function (res) {
return res.json();
})
.then(function (data) {
console.log(data);
});
الآن قيمة data
ستكون البيانات كـ json
{
"name": "Redmi 9",
"brand": "Xiaomi",
"colors-available": [
"gray",
"violet",
"green"
],
"ram": 4,
"space": 64,
"fast-charging": true,
"water-resistant": false
}
أول then
كانت تقوم بتحويل البيانات داخل الـ Response
إلى Promise
يحتوي على البيانات كـ Javascript object
وثاني then
استقبلت الـ Javascript object
العائد من الـ then
الاولى ثم قامت بطباعته
طبعًا يمكنك استخدام الـ catch
في حالة حدود أي reject
fetch('./file.json')
.then(function (res) {
return res.json();
})
.then(function (data) {
console.log(data);
})
.catch(function (error) {
console.log(error);
});
حسنًا يكفي هذا القدر عن الـ promise
سنتكلم عنه مجددًا وعن الـ Promise.all
بشيء من التفصيل لكن في مقالة التعامل مع أكثر من Promise في آن واحد
ولدينا مقالة أخرى تدعى الـ async-await بديل الـ Promise ؟ ستكون عن الـ async/await
وهي طريقة جديدة وبسيطة للتعامل مع الـ promise
أو الدوال التي تستغرق وقتًا بشكل عام