Promise في الجافاسكريبت وإدارة العمليات
السلام عليكم ورحمة الله وبركاته
المقدمة
في هذه المقالة سنتحدث عن الـ 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 في حالة fulfilledPromise.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التي بداخل هذا الـResponsestatus: متغير يحتوي على رمز الحالة الخاص بالـResponseok: قيمته تكونbooleanإذا كانت رمز حالة الـResponseما بين200و299قيمته تكونtrueعدا ذلك تكونfalsejson(): تقوم بتحويل البيانات التي في الـResponseإذا كانتJSONإلىJavascript Objectوترجعه فيpromiseblob(): تقوم بتحويل البيانات التي في الـ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 أو الدوال التي تستغرق وقتًا بشكل عام