يمثّل كائن Promise الاكتمال النهائي (أو الفشل) لعملية غير متزامنة والقيمة الناتجة عنها.
هذا موضوع صعب على كثيرين، لا سيما إن كنت تعرف البرمجة بلغة متزامنة بالكامل. إن شعرت بالإرهاق، أو أردت معرفة المزيد عن التزامن والتوازي، شاهد (عبر go.dev) أو شاهد مباشرة عبر vimeo واقرأ الشرائح من المحاضرة الرائعة «Concurrency is not parallelism».
لكائن Promise ثلاث حالات:
عند إنشائه، يكون الوعد قيد الانتظار. وفي وقتٍ ما في المستقبل قد يُحلّ أو يُرفض. وبمجرد أن يُحلّ الوعد أو يُرفض مرة واحدة، لا يمكن حلّه أو رفضه مرة أخرى، ولا يمكن أن تتغيّر حالته.
بعبارة أخرى:
يمكن حلّ الوعد بعدة أساليب:
// Creates a promise that is immediately resolved
Promise.resolve(value);
// Creates a promise that is immediately resolved
new Promise((resolve) => {
resolve(value);
});
// Chaining a promise leads to a resolved promise
somePromise.then(() => {
// ...
return value;
});
في الأمثلة أعلاه، يمكن أن تكون value أي شيء، بما في ذلك خطأ، أو undefined، أو null، أو وعد آخر.
عادةً تريد حلّ الوعد بقيمة ليست خطأً.
يمكن رفض الوعد بعدة أساليب:
// Creates a promise that is immediately rejected
Promise.reject(reason)
// Creates a promise that is immediately rejected
new Promise((_, reject) {
reject(reason)
})
// Chaining a promise with an error leads to a rejected promise
somePromise.then(() => {
// ...
throw reason
})
في الأمثلة أعلاه، يمكن أن يكون reason أي شيء، بما في ذلك خطأ، أو undefined، أو null.
عادةً تريد رفض الوعد بخطأ.
يمكن متابعة الوعد بإجراء لاحق بمجرد أن يُحلّ أو يُرفض.
promise.then() بمجرد أن يُحلّ promise
promise.catch() بمجرد أن يُرفض promise
promise.finally() بمجرد أن يُحلّ promise أو يُرفضكل وعد هو «thenable».
ويعني ذلك أن هناك دالة then متاحة ستُنفَّذ بمجرد أن يُحلّ الوعد الأصلي.
بمعطى promise.then(onResolved)، تتلقّى دالة الاستدعاء الراجع onResolved القيمة التي حُلّ بها الوعد الأصلي.
وسيُرجع هذا دائمًا وعدًا جديدًا «متسلسلًا».
إرجاع value من then يحلّ الوعد «المتسلسل».
ورمي reason في then يرفض الوعد «المتسلسل».
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
});
const promise2 = promise1.then(function (value) {
console.log(value);
// expected output: "Success!"
return true;
});
سيسجّل هذا "Success!" بعد حوالي 1000 مللي ثانية.
ستكون حالة promise1 وقيمته resolved و"Success!".
وستكون حالة promise2 وقيمته resolved وtrue.
يوجد وسيط ثانٍ متاح يُنفَّذ عندما يُرفض الوعد الأصلي.
بمعطى promise.then(onResolved, onRejected)، تتلقّى دالة الاستدعاء الراجع onResolved القيمة التي حُلّ بها الوعد الأصلي، أو تتلقّى دالة الاستدعاء الراجع onRejected سبب رفض الوعد.
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
if (Math.random() < 0.5) {
reject('Nope!');
}
});
function log(value) {
console.log(value);
return true;
}
function shout(reason) {
console.error(reason.toUpperCase());
return false;
}
const promise2 = promise1.then(log, shout);
"Success!" بعد حوالي 1000 مللي ثانية.
promise1 وقيمته resolved و"Success!".promise2 وقيمته resolved وtrue."NOPE!" فورًا.
promise1 وقيمته rejected وNope!.promise2 وقيمته resolved وfalse.من المهم أن تفهم أنه بسبب قواعد دورة الحياة، عندما يُستدعى reject، فإن resolve الذي يأتي بعد نحو 1000 مللي ثانية يُتجاهل بصمت، لأن الحالة الداخلية لا يمكن أن تتغيّر بمجرد أن رُفض الوعد أو حُلّ.
ومن المهم أن تفهم أن إرجاع قيمة من وعد يحلّه، ورمي قيمة يرفضه.
عندما يُحلّ promise1 وتوجد دالة onResolved متسلسلة: then(onResolved)، فإن الوعد الناتج عن ذلك وعد جديد يمكن أن يُحلّ أو يُرفض.
وعندما يُرفض promise1 ولكن توجد دالة onRejected متسلسلة: then(, onRejected)، فإن الوعد الناتج عن ذلك وعد جديد يمكن أن يُحلّ أو يُرفض.
أحيانًا تريد التقاط الأخطاء والاستمرار فقط عندما يُرفض الوعد الأصلي.
بمعطى promise.catch(onCatch)، تتلقّى دالة الاستدعاء الراجع onCatch سبب رفض الوعد الأصلي.
وسيُرجع هذا دائمًا وعدًا جديدًا «متسلسلًا».
إرجاع value من catch يحلّ الوعد «المتسلسل».
ورمي reason في catch يرفض الوعد «المتسلسل».
const promise1 = new Promise(function (resolve, reject) {
setTimeout(() => {
resolve('Success!');
}, 1000);
if (Math.random() < 0.5) {
reject('Nope!');
}
});
function log(value) {
console.log(value);
return 'done';
}
function recover(reason) {
console.error(reason.toUpperCase());
return 42;
}
const promise2 = promise1.catch(recover).then(log);
في نحو 1/2 من الحالات، سيسجّل هذا "Success!" بعد حوالي 1000 مللي ثانية.
وفي 1/2 الأخرى من الحالات، سيسجّل هذا 42 فورًا.
promise1، يُتخطّى catch ويصل التنفيذ إلى then، ويسجّل القيمة.
promise1 وقيمته resolved و"Success!".promise2 وقيمته resolved و"done"؛promise1، يُنفَّذ catch، الذي يُرجع قيمة، وبذلك يصبح التسلسل الآن resolved، ويصل التنفيذ إلى then، ويسجّل القيمة.
promise1 وقيمته rejected و"Nope!".promise2 وقيمته resolved و"done"؛أحيانًا تريد تنفيذ كود بعد أن يستقرّ الوعد، بغض النظر عن كونه يُحلّ أو يُرفض.
بمعطى promise.finally(onSettled)، لا تتلقّى دالة الاستدعاء الراجع onSettled شيئًا.
وسيُرجع هذا دائمًا وعدًا جديدًا «متسلسلًا».
إرجاع value من finally ينسخ الحالة والقيمة من الوعد الأصلي، متجاهلًا value.
ورمي reason في finally يرفض الوعد «المتسلسل»، ويلغي أي حالة وقيمة أو سبب من الوعد الأصلي.
بعض الطرق مجتمعة:
const myPromise = new Promise(function (resolve, reject) {
const sampleData = [2, 4, 6, 8];
const randomNumber = Math.round(Math.random() * 5);
if (sampleData[randomNumber]) {
resolve(sampleData[randomNumber]);
} else {
reject('Sampling did not result in a sample');
}
});
const finalPromise = myPromise
.then(function (sampled) {
// If the random number was 0, 1, 2, or 3, this will be
// reached and the number 2, 4, 6, or 8 will be logged.
console.log(`Sampled data: ${sampled}`);
return 'yay';
})
.catch(function (reason) {
// If the random number was 4 or 5, this will be reached and
// reason will be "An error occurred". The entire chain will
// then reject with an Error with the reason as message.
throw new Error(reason);
})
.finally(function () {
// This will always log after either the sampled data is
// logged or the error is raised.
console.log('Promise completed');
});
randomNumber هي 0-3:
myPromise بالقيمة 2, 4, 6, or 8
finalPromise بالقيمة 'yay'
Sampled data: ...Promise completedrandomNumber هي 4-5:
myPromise بالسبب 'Sampling did not result in a sample'
finalPromise بالسبب Error('Sampling did not result in a sample')
Promise completed"uncaught rejected promise: Error('Sampling did not result in a sample')"
كما هو موضّح أعلاه، يعمل reject مع سلسلة نصية، ويمكن للوعد أيضًا أن يُرفض بـError.
إن كان تسلسل الوعود أو الاستخدام العام غير واضح، فإن الدرس التعليمي على MDN مصدر جيد للاطلاع عليه.