{"id":"l-at-5-2","courseId":"advanced-typescript","lang":"ar","kind":"lesson","summary":"حرّر الموارد بموثوقية باستخدام تصريحات using وSymbol.dispose، واكتب decorators قياسية محدَّدة الأنواع للسلوك العابر للأجزاء مثل التسجيل وإعادة المحاولة.","html":"<p>يحجز نظام الدفع في Cartwheel المخزون قبل الخصم من البطاقة، ثم يحرّر الحجز إن فشل أي شيء. النسخة الأولى من ذلك الكود بدت هكذا:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> placeOrder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">skus</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[]) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> reservations</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> skus.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">sku</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> reserve</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(sku));</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  chargeCard</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// throws on a declined card</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  reservations.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">forEach</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">r</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> r.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">release</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">());</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>البطاقة المرفوضة تتخطّى السطر الأخير، فيبقى المخزون مقفلًا حتى تلاحظه مهمة ليلية. الإصلاح التقليدي هو <code dir=\"ltr\">try</code>/<code dir=\"ltr\">finally</code>، متداخلة مرة لكل مورد. هذا الدرس يغطّي جواب اللغة المدمج، ثم ميزة حديثة ثانية تُبقي الكود العابر للأجزاء مثل إعادة المحاولة خارج منطق العمل: الـ decorators القياسية.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">using: تنظيف مربوط بالنطاق</h2>\n<p>تصريح <code dir=\"ltr\">using</code> مثل <code dir=\"ltr\">const</code>، مع إضافة واحدة: حين تنتهي الكتلة التي يعيش فيها، بـ <code dir=\"ltr\">return</code> أو بالوصول إلى نهايتها أو بـ <code dir=\"ltr\">throw</code>، تستدعي JavaScript الـ method المسمّاة <code dir=\"ltr\">[Symbol.dispose]()</code> على القيمة.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> reserve</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">sku</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Disposable</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`reserve ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">sku</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { [Symbol.dispose]: () </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`release ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">sku</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> placeOrder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  using</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> tent</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> reserve</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'TENT-2P'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  using</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> mug</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> reserve</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'MUG-SET'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  chargeCard</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(); </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// even if this throws…</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}               </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// …both are released here: MUG-SET first, then TENT-2P</span></span></code></pre></div>\n<p>تصريحات <code dir=\"ltr\">using</code> المتعددة تُحرَّر بترتيب معكوس، تمامًا مثل كتل <code dir=\"ltr\">finally</code> المتداخلة، فيُحرَّر المورد دائمًا قبل الموارد التي أُنشئ بعدها. وإن رمى التحرير نفسه خطأً بينما خطأ آخر قيد الانتشار، يُحفظ الاثنان في <code dir=\"ltr\">SuppressedError</code> بدل أن يحلّ أحدهما محلّ الآخر بصمت.</p>\n<p>للتنظيف غير المتزامن، مثل إغلاق معاملة قاعدة بيانات، استخدم <code dir=\"ltr\">await using</code> مع كائن فيه <code dir=\"ltr\">[Symbol.asyncDispose]()</code> تُعيد promise. وحين يكون عدد الموارد ديناميكيًا، اجمعها في <code dir=\"ltr\">DisposableStack</code> (أو <code dir=\"ltr\">AsyncDisposableStack</code>)، وهي نفسها قابلة للتحرير:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> placeOrder</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">skus</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[]) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  using</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> stack</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> DisposableStack</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> sku</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> of</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> skus) stack.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">use</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">reserve</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(sku));</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  chargeCard</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">} </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// every reservation released, in reverse order</span></span></code></pre></div>\n<p>وصلت <code dir=\"ltr\">using</code> في TypeScript 5.2. وهي تعمل أصليًا في Node 24 وفي Chrome وEdge منذ الإصدار 134 وFirefox منذ 141، لكن Safari لا يدعمها بعد (حتى أواخر 2026)، فهي ليست Baseline. وللكود الذي يصل إلى المتصفّحات، دع TypeScript أو الـ bundler يحوّل الصياغة إلى صيغة أقدم، وتأكّد من وجود <code dir=\"ltr\">Symbol.dispose</code> في بيئة التشغيل (يوفّره polyfill حيث يغيب). والأنواع موجودة في الـ lib المسمّاة <code dir=\"ltr\">esnext.disposable</code> (المضمّنة في <code dir=\"ltr\">&quot;lib&quot;: [&quot;esnext&quot;]</code>). بيئة تجربة التمارين تستخدم الـ lib الخاصة بـ ES2022، فيعلن كود البداية هاتين القطعتين بنفسه، وهي معاينة لدمج التصريحات الذي ستتعلّمه في الدرس القادم.</p>\n<h3 id=\"sec-2\" tabindex=\"-1\">جعل الـ APIs الخاصة بك قابلة للتحرير</h3>\n<p>تؤتي <code dir=\"ltr\">using</code> أكبر ثمارها حين تُعيد الـ APIs الخاصة بك قيمًا قابلة للتحرير. الـ emitter من <a href=\"/ar/lesson/advanced-typescript/l-at-4-4\">باعثات أحداث وبنّاؤون آمنون من حيث النوع</a> أعاد دالة <code dir=\"ltr\">off</code>؛ وإعادة كائن فيه <code dir=\"ltr\">[Symbol.dispose]</code> أيضًا تتيح للمستدعين حصر الاشتراك في كتلة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> subscribe</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> keyof</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> CheckoutEvents</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">event</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> K</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">handler</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">p</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> CheckoutEvents</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[</span><span style=\"color:#953800;--shiki-dark:#FFA657\">K</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> void</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Disposable</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> off</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> bus.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">on</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(event, handler);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { [Symbol.dispose]: off };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">async</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> waitForShipment</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">orderId</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  using</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> _</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> subscribe</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'order.shipped'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, (</span><span style=\"color:#953800;--shiki-dark:#FFA657\">e</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (e.orderId </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">===</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> orderId) </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">notify</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(e); });</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  await</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> pollUntilShipped</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(orderId);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">} </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// listener removed here, however the function ends</span></span></code></pre></div>\n<p>المرشّحون الجيدون كل ما له “تراجع” مقابل: الأقفال والحجوزات، واشتراكات الأحداث، والمؤقّتات، والملفات المؤقتة، ومعاملات قواعد البيانات (مع <code dir=\"ltr\">await using</code>). إن وجدت نفسك تكتب <code dir=\"ltr\">try</code>/<code dir=\"ltr\">finally</code> فقط لاستدعاء method تنظيف، فذلك المورد يريد <code dir=\"ltr\">[Symbol.dispose]</code>.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>نسيان أن using تعمل لكل كتلة</span></p>\n<p><code dir=\"ltr\">using</code> تحرّر عند نهاية <em>الكتلة</em>، لا الدالة. أعلن حجزًا داخل <code dir=\"ltr\">if</code> أو جسم حلقة تكرار فيُحرَّر حين تنتهي تلك الكتلة، ربما قبل أن تخصم من البطاقة. ضع <code dir=\"ltr\">using</code> في الكتلة التي يطابق عمرها عمر المورد.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">الـ decorators القياسية</h2>\n<p>الـ decorator (المُزخرِف) دالة تُطبَّق بـ <code dir=\"ltr\">@</code> على صنف أو عضو في صنف. نفّذ TypeScript 5.0 الـ decorators القياسية (TC39)، ويترجمها TypeScript إلى استدعاءات دوال عادية، فتعمل في أي بيئة تشغيل. يتلقّى method decorator الـ method الأصلية وكائن <code dir=\"ltr\">context</code>، ويمكنه إعادة بديل:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> logged</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">This</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">Args</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> unknown</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">Return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  method</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">this</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> This</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#953800;--shiki-dark:#FFA657\">args</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Args</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">  context</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ClassMethodDecoratorContext</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">This</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, (</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">this</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> This</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#953800;--shiki-dark:#FFA657\">args</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Args</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> name</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> String</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(context.name);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">this</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> This</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#953800;--shiki-dark:#FFA657\">args</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Args</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`→ ${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">name</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">}`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, args);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> method.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">call</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">args);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">class</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> PaymentGateway</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  @logged</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  charge</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">amountCents</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">currency</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) { </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* … */</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>الـ generics تجعل الـ decorator يعمل على أي method دون <code dir=\"ltr\">any</code>: تُستنتج <code dir=\"ltr\">This</code> و<code dir=\"ltr\">Args</code> و<code dir=\"ltr\">Return</code> من الـ method التي يزخرفها، ويحتفظ البديل بالتوقيع نفسه. والـ decorators التي تأخذ خيارات هي <strong>مصانع decorators (decorator factories)</strong>، أي دوال تُعيد الـ decorator:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> retry</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">times</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> &#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">This</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">Args</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> extends</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> unknown</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">[], </span><span style=\"color:#953800;--shiki-dark:#FFA657\">Return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>(</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">    method</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">this</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> This</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#953800;--shiki-dark:#FFA657\">args</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Args</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">,</span></span>\n<span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">    _context</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> ClassMethodDecoratorContext</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">&#x3C;</span><span style=\"color:#953800;--shiki-dark:#FFA657\">This</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, (</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">this</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> This</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#953800;--shiki-dark:#FFA657\">args</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Args</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">>,</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  ) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">    return</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> function</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">this</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> This</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#953800;--shiki-dark:#FFA657\">args</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Args</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> Return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">      for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">let</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> attempt </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">; ; attempt</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">++</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">        try</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">          return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> method.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">call</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">this</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">args);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">catch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (e) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">          if</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (attempt </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">>=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> times) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">throw</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> e;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">        }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">      }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">    };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  };</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">class</span><span style=\"color:#953800;--shiki-dark:#FFA657\"> PaymentGateway</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  @</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">retry</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">3</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)</span></span>\n<span class=\"line\"><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">  charge</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">amountCents</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> number</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">currency</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> string</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) { </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">/* … */</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> }</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span></code></pre></div>\n<p>الـ decorators المكدّسة تُطبَّق من الأسفل إلى الأعلى. مع <code dir=\"ltr\">@logged</code> فوق <code dir=\"ltr\">@retry(3)</code>، يُطبَّق غلاف إعادة المحاولة على <code dir=\"ltr\">charge</code> أولًا ثم يغلّف <code dir=\"ltr\">logged</code> النتيجة، فتحصل على سطر تسجيل واحد لكل استدعاء لا لكل محاولة. بدّلهما فتسجّل كل محاولة. لا أحد منهما خاطئ، لكن الترتيب سلوك، فاختره عن قصد.</p>\n<p>ويقدّم <code dir=\"ltr\">context</code> أيضًا <code dir=\"ltr\">context.name</code> و<code dir=\"ltr\">context.static</code> و<code dir=\"ltr\">context.private</code> و<code dir=\"ltr\">context.addInitializer(fn)</code>، التي تنفّذ كودًا حين تُنشأ نسخة (وهي الطريقة المعتادة لكتابة decorator مثل <code dir=\"ltr\">@bound</code> يربط method بنسختها).</p>\n<aside class=\"callout callout-why\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">لماذا يهمّ هذا</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>الـ decorators القياسية مقابل القديمة</span></p>\n<p>كثير من الأطر القائمة (Angular وNestJS وTypeORM) بُنيت على <code dir=\"ltr\">experimentalDecorators</code> الأقدم في TypeScript، التي تستخدم توقيعًا مختلفًا <code dir=\"ltr\">(target, key, descriptor)</code> وتدعم parameter decorators و<code dir=\"ltr\">emitDecoratorMetadata</code>. لا يمكن خلط النظامين في ترجمة واحدة. إن تطلّب إطارٌ الخيار القديم فاتبعه؛ أما للكود الجديد دون إطار كهذا فاستخدم الـ decorators القياسية، واستخدمها باعتدال. الدالة العادية ذات الرتبة الأعلى، <code dir=\"ltr\">const charge = withRetry(3, rawCharge)</code>، كثيرًا ما تكون أوضح ولا تحتاج إلى صنف.</p>\n</aside>\n<p>في التمرين ستحرّر حجوزات المخزون بـ <code dir=\"ltr\">using</code> وتضيف <code dir=\"ltr\">@retry</code> محدَّد النوع إلى بوابة الدفع في Cartwheel. الدرس القادم: وصف كود لم تكتبه، بملفات التصريحات.</p>\n","toc":[{"id":"sec-1","text":"using: تنظيف مربوط بالنطاق","level":2},{"id":"sec-2","text":"جعل الـ APIs الخاصة بك قابلة للتحرير","level":3},{"id":"sec-3","text":"الـ decorators القياسية","level":2}],"takeaways":["تصريح <code dir=\"ltr\">using</code> يستدعي <code dir=\"ltr\">[Symbol.dispose]()</code> على القيمة حين تنتهي الكتلة المحيطة، سواء عادت بشكل طبيعي أو رمت خطأً.","تصريحات <code dir=\"ltr\">using</code> المتعددة في كتلة واحدة تُحرَّر بترتيب معكوس، مثل كتل <code dir=\"ltr\">try</code>/<code dir=\"ltr\">finally</code> المتداخلة.","<code dir=\"ltr\">await using</code> و<code dir=\"ltr\">Symbol.asyncDispose</code> يفعلان الشيء نفسه للتنظيف الذي يُعيد promise.","الـ decorators القياسية (منذ TypeScript 5.0) دوال بالشكل <code dir=\"ltr\">(value, context)</code> يمكنها إعادة بديل؛ وهي ليست نفسها <code dir=\"ltr\">experimentalDecorators</code> الأقدم.","حدّد نوع method decorator بـ <code dir=\"ltr\">ClassMethodDecoratorContext</code> ومعاملات generic هي <code dir=\"ltr\">This</code> و<code dir=\"ltr\">Args</code> و<code dir=\"ltr\">Return</code> كي يعمل على أي method دون <code dir=\"ltr\">any</code>."],"further":[{"title":"using declarations (TypeScript 5.2 release notes)","url":"https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html"},{"title":"Decorators (TypeScript 5.0 release notes)","url":"https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html"},{"title":"using (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using"}],"quiz":[{"q":"<p>ماذا يطبع هذا؟</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"ts\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">TypeScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> checkout</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">() {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  using</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> a</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> reserve</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'TENT-2P'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// logs \"reserve TENT-2P\", disposes with \"release TENT-2P\"</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  using</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> b</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> reserve</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'MUG-SET'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);   </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// logs \"reserve MUG-SET\", disposes with \"release MUG-SET\"</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  throw</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Error</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'card declined'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">try</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> { </span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">checkout</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(); } </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">catch</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {}</span></span></code></pre></div>\n","options":[{"text":"reserve TENT-2P, reserve MUG-SET, release MUG-SET, release TENT-2P","why":"صحيح. الرمي يُخرج من الكتلة، فيُحرَّر المورِدان كلاهما، ويجري التحرير بعكس ترتيب التصريح."},{"text":"reserve TENT-2P, reserve MUG-SET, release TENT-2P, release MUG-SET","why":"التحرير يتبع “الداخل أخيرًا يخرج أولًا”، مثل كتل <code dir=\"ltr\">finally</code> المتداخلة، فيُحرَّر <code dir=\"ltr\">b</code> قبل <code dir=\"ltr\">a</code>."},{"text":"reserve TENT-2P, reserve MUG-SET (لا شيء يُحرَّر لأن الدالة رمت خطأً)","why":"التحرير عند كل مسار خروج، بما فيه الرمي، هو الغاية كلها من <code dir=\"ltr\">using</code>."},{"text":"لا شيء؛ لا يمكن الجمع بين <code dir=\"ltr\">using</code> و<code dir=\"ltr\">throw</code> في الكتلة نفسها.","why":"لا يوجد قيد كهذا؛ الرمي أحد مسارات الخروج التي صُمّمت <code dir=\"ltr\">using</code> لأجلها."}],"answer":0},{"q":"<p>أيّ قيمة يمكن ربطها بـ <code dir=\"ltr\">using</code>؟</p>","options":[{"text":"أي كائن فيه method باسم <code dir=\"ltr\">close()</code>.","why":"<code dir=\"ltr\">using</code> لا تبحث عن <code dir=\"ltr\">close()</code>؛ بل تستدعي الـ method المخزّنة تحت <code dir=\"ltr\">Symbol.dispose</code>."},{"text":"كائن فيه method باسم <code dir=\"ltr\">[Symbol.dispose]()</code>، أو <code dir=\"ltr\">null</code>/<code dir=\"ltr\">undefined</code>.","why":"صحيح. يجب أن تكون القيمة قابلة للتحرير؛ و<code dir=\"ltr\">null</code> و<code dir=\"ltr\">undefined</code> مسموحتان وتُتخطّيان ببساطة."},{"text":"نُسخ <code dir=\"ltr\">DisposableStack</code> فقط.","why":"<code dir=\"ltr\">DisposableStack</code> أداة مساعدة لجمع القيم القابلة للتحرير، لا شرط."}],"answer":1},{"q":"<p>كيف يتلقّى method decorator القياسي الـ method التي يزخرفها؟</p>","options":[{"text":"بالشكل <code dir=\"ltr\">(target, propertyKey, descriptor)</code>، ويعدّل الـ descriptor.","why":"هذا توقيع <code dir=\"ltr\">experimentalDecorators</code> القديم. الـ decorators القياسية تتلقّى <code dir=\"ltr\">(value, context)</code>."},{"text":"كاسم نصي، ويبحث عن الـ method على <code dir=\"ltr\">this</code>.","why":"الـ method نفسها تُمرَّر كوسيط أول؛ والاسم متاح في <code dir=\"ltr\">context.name</code>."},{"text":"بالشكل <code dir=\"ltr\">(value, context)</code>، حيث <code dir=\"ltr\">value</code> هي الـ method ويمكن للـ decorator أن يُعيد دالة بديلة.","why":"صحيح. إعادة دالة جديدة تستبدل الـ method، وهكذا تعمل decorators التسجيل وإعادة المحاولة."}],"answer":2},{"q":"<p>تستخدم قاعدة الكود عندك parameter decorators على طريقة NestJS مثل <code dir=\"ltr\">constructor(@Inject(TOKEN) svc)</code>. ماذا يعني ذلك لـ tsconfig الخاص بك؟</p>","options":[{"text":"لا شيء؛ الـ decorators القياسية تدعم المعاملات.","why":"الـ decorators القياسية لا تدعم parameter decorators، فتُرفض هذه الصياغة دون الخيار القديم."},{"text":"الـ parameter decorators تحتاج <code dir=\"ltr\">&quot;experimentalDecorators&quot;: true</code>، الذي يحوّل المشروع كله إلى دلالات الـ decorators القديمة.","why":"صحيح. لا يمكن خلط النظامين في ترجمة واحدة؛ والأطر المبنية على النموذج القديم تحتاج هذا الخيار."},{"text":"يجب ضبط <code dir=\"ltr\">&quot;target&quot;: &quot;esnext&quot;</code> كي تتعامل بيئة التشغيل معها.","why":"الـ target لا يغيّر نظام الـ decorators الذي يستخدمه TypeScript؛ الخيار هو من يغيّره."},{"text":"يجب تفعيل <code dir=\"ltr\">emitDecoratorMetadata</code> وحده.","why":"هذا الخيار لا يعمل إلا مع <code dir=\"ltr\">experimentalDecorators</code>؛ ووحده لا يفعل شيئًا للـ parameter decorators."}],"answer":1}],"exercise":{"kind":"web","mode":"ts","starter":{"ts":"// ---- Playground shim (do not edit) ----\n// The playground's lib is ES2022. These lines add what lib.esnext.disposable\n// would declare, and make sure Symbol.dispose exists at runtime.\ninterface SymbolConstructor { readonly dispose: unique symbol; }\ninterface Disposable { [Symbol.dispose](): void; }\n(Symbol as { dispose?: symbol }).dispose ??= Symbol.for('Symbol.dispose');\n// ----------------------------------------\n\nconst events: string[] = [];\n\nfunction reserve(sku: string) {\n  events.push(`reserve ${sku}`);\n  return { sku, release: () => events.push(`release ${sku}`) };\n}\n\nclass PaymentGateway {\n  calls = 0;\n  failuresLeft: number;\n\n  constructor(failures: number) {\n    this.failuresLeft = failures;\n  }\n\n  // Should be retried up to 3 times in total.\n  charge(amountCents: number): string {\n    this.calls++;\n    if (this.failuresLeft > 0) {\n      this.failuresLeft--;\n      throw new Error('Gateway timeout');\n    }\n    events.push(`charge ${amountCents}`);\n    return `receipt-${amountCents}`;\n  }\n}\n\nfunction placeOrder(skus: string[], gateway: PaymentGateway): string {\n  const reservations = skus.map((sku) => reserve(sku));\n  const receipt = gateway.charge(skus.length * 1000);\n  reservations.forEach((r) => r.release());\n  return receipt;\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<ReturnType<typeof reserve>[typeof Symbol.dispose], () => void>>;\ntype T2 = Expect<Equal<ReturnType<PaymentGateway['charge']>, string>>;\n\nconsole.log(placeOrder(['TENT-2P', 'MUG-SET'], new PaymentGateway(0)), events);\n"},"solution":{"ts":"// ---- Playground shim (do not edit) ----\n// The playground's lib is ES2022. These lines add what lib.esnext.disposable\n// would declare, and make sure Symbol.dispose exists at runtime.\ninterface SymbolConstructor { readonly dispose: unique symbol; }\ninterface Disposable { [Symbol.dispose](): void; }\n(Symbol as { dispose?: symbol }).dispose ??= Symbol.for('Symbol.dispose');\n// ----------------------------------------\n\nconst events: string[] = [];\n\nfunction reserve(sku: string) {\n  events.push(`reserve ${sku}`);\n  return {\n    sku,\n    [Symbol.dispose]: () => {\n      events.push(`release ${sku}`);\n    },\n  };\n}\n\nfunction retry(times: number) {\n  return function <This, Args extends unknown[], Return>(\n    method: (this: This, ...args: Args) => Return,\n    _context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>,\n  ) {\n    return function (this: This, ...args: Args): Return {\n      for (let attempt = 1; ; attempt++) {\n        try {\n          return method.call(this, ...args);\n        } catch (e) {\n          if (attempt >= times) throw e;\n        }\n      }\n    };\n  };\n}\n\nclass PaymentGateway {\n  calls = 0;\n  failuresLeft: number;\n\n  constructor(failures: number) {\n    this.failuresLeft = failures;\n  }\n\n  @retry(3)\n  charge(amountCents: number): string {\n    this.calls++;\n    if (this.failuresLeft > 0) {\n      this.failuresLeft--;\n      throw new Error('Gateway timeout');\n    }\n    events.push(`charge ${amountCents}`);\n    return `receipt-${amountCents}`;\n  }\n}\n\nfunction placeOrder(skus: string[], gateway: PaymentGateway): string {\n  // Each reservation is released when this function exits, in reverse order.\n  using stack = new DisposableStackLite();\n  for (const sku of skus) stack.use(reserve(sku));\n  return gateway.charge(skus.length * 1000);\n}\n\n// A tiny stand-in for DisposableStack, which the ES2022 playground lib lacks.\nclass DisposableStackLite implements Disposable {\n  #items: Disposable[] = [];\n  use<T extends Disposable>(item: T): T {\n    this.#items.push(item);\n    return item;\n  }\n  [Symbol.dispose]() {\n    for (const item of this.#items.reverse()) item[Symbol.dispose]();\n  }\n}\n\n// ---- Type tests (do not edit) ----\ntype T1 = Expect<Equal<ReturnType<typeof reserve>[typeof Symbol.dispose], () => void>>;\ntype T2 = Expect<Equal<ReturnType<PaymentGateway['charge']>, string>>;\n\nconsole.log(placeOrder(['TENT-2P', 'MUG-SET'], new PaymentGateway(0)), events);\n"},"checks":[{"id":"types","typecheck":true},{"id":"releases","test":"events.length = 0; placeOrder(['A', 'B'], new PaymentGateway(0)); return events.join('|') === 'reserve A|reserve B|charge 2000|release B|release A';"},{"id":"releases-on-error","test":"events.length = 0; try { placeOrder(['A', 'B'], new PaymentGateway(5)); return false; } catch { return events.join('|') === 'reserve A|reserve B|release B|release A'; }"},{"id":"retries","test":"const g = new PaymentGateway(2); const r = g.charge(500); return r === 'receipt-500' && g.calls === 3;"},{"id":"gives-up","test":"const g = new PaymentGateway(3); try { g.charge(500); return false; } catch (e) { return g.calls === 3 && String(e.message).includes('timeout'); }"}],"title":"حرّر المخزون، وأعِد محاولة الخصم من البطاقة","prompt":"<p>إصلاحان لنظام الدفع في Cartwheel:</p>\n<ol>\n<li>اجعل <code dir=\"ltr\">reserve</code> تُعيد قيمة قابلة للتحرير (فيها method باسم <code dir=\"ltr\">[Symbol.dispose]</code> تطبع <code dir=\"ltr\">release &lt;sku&gt;</code> بدل method باسم <code dir=\"ltr\">release</code>)، وأعِد كتابة <code dir=\"ltr\">placeOrder</code> باستخدام <code dir=\"ltr\">using</code> بحيث يُحرَّر كل حجز حين تنتهي الدالة، <strong>بترتيب معكوس</strong>، حتى حين يرمي الخصم من البطاقة خطأً.</li>\n<li>اكتب مصنع decorator محدَّد الأنواع <code dir=\"ltr\">retry(times)</code> وطبّق <code dir=\"ltr\">@retry(3)</code> على <code dir=\"ltr\">PaymentGateway.charge</code>، بحيث يُحاوَل الخصم من البطاقة الذي يرمي خطأً حتى 3 مرات إجمالًا قبل أن ينتشر الخطأ. أبقِ توقيع <code dir=\"ltr\">charge</code> سليمًا.</li>\n</ol>\n<p>أعلى الملف يعلن <code dir=\"ltr\">Symbol.dispose</code> و<code dir=\"ltr\">Disposable</code>، لأن بيئة التجربة تستخدم الـ lib الخاصة بـ ES2022. <code dir=\"ltr\">DisposableStack</code> غير متاحة هنا؛ إن احتجت إليها، فاكتب صنفًا صغيرًا خاصًا بك.</p>\n","hints":["<code dir=\"ltr\">using r = reserve('A');</code> تحرّر <code dir=\"ltr\">r</code> عند نهاية الكتلة. لقائمة ديناميكية من رموز المنتجات، اجمع القيم القابلة للتحرير في صنف صغير فيه method باسم <code dir=\"ltr\">use(item)</code> و<code dir=\"ltr\">[Symbol.dispose]()</code> تحرّرها بترتيب معكوس.","الـ method decorator القياسي شكله <code dir=\"ltr\">(method, context: ClassMethodDecoratorContext&lt;This, …&gt;) =&gt; replacementFunction</code>. والمصنع دالة تُعيد واحدًا: <code dir=\"ltr\">function retry(times: number) { return function &lt;This, Args extends unknown[], Return&gt;(method, context) { … } }</code>.","داخل البديل، كرّر: <code dir=\"ltr\">try { return method.call(this, ...args); } catch (e) { if (attempt &gt;= times) throw e; }</code>."],"explanation":"<p>تربط <code dir=\"ltr\">using</code> عمر المورد بكتلة: أيًّا كانت طريقة خروج <code dir=\"ltr\">placeOrder</code>، تُحرَّر الحاوية، وهي تحرّر الحجوزات بترتيب “الداخل أخيرًا يخرج أولًا”، فيزول تسرّب كود البداية عند رفض الخصم من البطاقة. يلتقط مصنع الـ decorator قيمة <code dir=\"ltr\">times</code> ويُعيد method decorator قياسيًا؛ وتُستنتج <code dir=\"ltr\">This</code> و<code dir=\"ltr\">Args</code> و<code dir=\"ltr\">Return</code> الـ generic من <code dir=\"ltr\">charge</code>، فيحتفظ البديل بتوقيعها بالضبط (اختبار <code dir=\"ltr\">T2</code>) ويستدعي الأصل بـ <code dir=\"ltr\">method.call(this, ...args)</code>. ومنطق إعادة المحاولة يعيش الآن في مكان واحد قابل لإعادة الاستخدام بدل أن يكون داخل كود الدفع.</p>\n","checkLabels":{"types":"الملف يجتاز فحص الأنواع بلا أي خطأ، بما فيها اختبارات الأنواع","releases":"تُحرَّر الحجوزات بعد الخصم من البطاقة، بترتيب معكوس","releases-on-error":"تُحرَّر الحجوزات حتى حين يفشل الخصم من البطاقة نهائيًا","retries":"ينجح <code dir=\"ltr\">charge</code> في المحاولة الثالثة بعد إخفاقين","gives-up":"يستسلم <code dir=\"ltr\">charge</code> ويعيد رمي الخطأ بعد 3 محاولات"}},"words":655,"readMinutes":4}