في TypeScript يكون نوع الـ promise هو Promise<T>، حيث T نوع القيمة التي يُحل بها. الدالة التي تعيد promise تصرّح بذلك في نوع القيمة المُعادة، والقيمة التي تحصل عليها من then أو await نوعها T.
آليات الـ promise (الحالات، وطابور المهام الدقيقة، والتسلسل) هي JavaScript عادية، مشروحة في الـ promises في JavaScript. هذه الصفحة عن الأنواع.
تحديد نوع new Promise وresolve
عندما يكون new Promise القيمة المُعادة من دالة لها نوع إعادة مصرّح به، يأتي T من ذلك النوع. أما وحده فلا يستنتج TypeScript النوع T من استدعاءات resolve، فتحصل على Promise<unknown>. مرّر وسيط النوع صراحة:
تفصيلان. نوع resolve مأخوذ من T، لذلك يكون resolve("42") في Promise<number> خطأ ترجمة. واستدعاء resolve() دون وسيط مسموح فقط عندما يتضمن T النوع void: في Promise<number> يكون الخطأ TS2794 (Expected 1 arguments, but got 0. Did you forget to include 'void' in your type argument to 'Promise'?).
جانب reject ليس محدد النوع. لا يوجد Promise<T, E>: يمكن أن يُرفض الـ promise بأي قيمة.
then وcatch وfinally
كل then تعيد promise جديدًا نوعه مأخوذ مما تعيده دالتها. إذا أعادت الدالة promise يفكّه TypeScript، فلا تحصل أبدًا على Promise<Promise<T>>.
معامل دالة catch نوعه any في المكتبة القياسية، لا unknown. لا شيء يمنعك من كتابة e.message على قيمة قد تكون نصًا. حدد نوعه كـ unknown، كما في المثال، وضيّقه قبل الاستخدام. أما try/catch حول await فأفضل: مع strict يكون متغير catch فيها unknown أصلًا (راجع معالجة الأخطاء).
Promise.all يعيد Tuple
عند تمرير مصفوفة حرفية من promises مختلفة الأنواع، يعيد Promise.all promise لـ tuple، ويحتفظ كل موضع فيه بنوعه الخاص:
يمكن خلط قيم ليست promises، وتمر كما هي دون تغيير. يُرفض Promise.all بمجرد رفض أحد المُدخلات، وتضيع النتائج الأخرى. يُحل Promise.race أو يُرفض بأول مُدخل يستقر، ونوعه union المُدخلات؛ ويُحل Promise.any بأول نجاح، ولا يُرفض بـ AggregateError إلا إذا فشلت كل المُدخلات.
Promise.allSettled ونوع نتيجته
ينتظر Promise.allSettled كل المُدخلات ولا يُرفض أبدًا. كل نتيجة من النوع PromiseSettledResult<T>، وهو union تضيّقه بالحقل status:
لا يوجد r.value إلا بعد فحص status، لأن الحالة المرفوضة ليس فيها value. ونوع reason هو any، للسبب نفسه كما في catch. تستخدم filter دالة type predicate حتى تُحدد المصفوفة الناتجة كنتائج ناجحة.
تغليف واجهة Callback في Promise
الواجهات الأقدم تُبلغ عن النتائج عبر دالة callback، غالبًا بأسلوب Node (err, result) => void. غلّفها مرة واحدة في دالة تعيد promise محدد النوع، ويستطيع بقية الكود استخدام await:
في Node يقوم util.promisify بذلك للدوال التي تتبع اصطلاح (err, result)، وكثير من الوحدات المدمجة لها أصلًا نسخ تعمل بالـ promises (node:fs/promises، node:timers/promises).
أخطاء شائعة
- إعادة
Tحيث يقول التوقيعPromise<T>، أو العكس. الدالة غير المتزامنة المصرّح بأنها تعيدPromise<User>يجب أن تعيد promise؛ أما دالةasyncفتعيد واحدًا تلقائيًا. - نسيان التعامل مع promise. استدعاء مثل
save(user);دونawaitأوthenأوcatchيُترجم دون مشكلة، ويصبح الرفض رفضًا غير معالج (ينهي عملية Node افتراضيًا). قاعدة typescript-eslintno-floating-promisesتكتشف هذه الحالات. - الثقة بنوع
.catch((e) => ...). المتغيرeفيها نوعهany. حدد نوعه كـunknown. - استخدام
new Promiseحول شيء يعيد promise أصلًا. أعد الـ promise الموجود أو استخدمawaitمعه مباشرة.
الأسئلة الشائعة
ما هو Promise<T> في TypeScript؟
Promise<T> هو نوع promise يُحل بقيمة من النوع T. الدالة التي تعيد Promise<string> تعطيك promise تحصل منه دالة then أو await على string. والـ promise الذي يُحل دون قيمة نوعه Promise<void>.
كيف أحدد نوع new Promise في TypeScript؟
مرّر وسيط النوع: new Promise<number>((resolve, reject) => ...). دونه لا يستطيع TypeScript استنتاج القيمة من استدعاءات resolve وتكون النتيجة Promise<unknown>. للـ promise الذي يُحل دون شيء استخدم new Promise<void>(resolve => ...) حتى يُسمح باستدعاء resolve() دون وسيط.
ما نوع الخطأ في catch الخاصة بالـ promise؟
any. المعامل reason في دالة catch نوعه any في المكتبة القياسية، لأن أي شيء يمكن أن يُرمى أو يُرفض به. حدد نوعه بنفسك كـ unknown (.catch((e: unknown) => ...)) وضيّقه بـ instanceof Error قبل استخدامه.
كيف يعمل Promise.all مع الأنواع في TypeScript؟
مع مصفوفة حرفية يعيد Promise.all نوع tuple فيه عنصر لكل مُدخل، بالترتيب: Promise.all([getUser(), getCount()]) نوعه Promise<[User, number]>، فيعطي التفكيك كل قيمة نوعها الخاص. ومع مصفوفة من نوع واحد، T[]، يعيد Promise<T[]>.
ما الفرق بين Promise.all وPromise.allSettled؟
يُرفض Promise.all بمجرد رفض أي مُدخل. أما Promise.allSettled فيُحل دائمًا، بمصفوفة من كائنات PromiseSettledResult<T>: { status: "fulfilled", value } أو { status: "rejected", reason }. افحص status لتضييق كل واحد منها.