{"id":"l-at-5-2","courseId":"advanced-typescript","lang":"en","kind":"lesson","summary":"Release resources reliably with using declarations and Symbol.dispose, and write typed standard decorators for cross-cutting behaviour such as logging and retries.","html":"<p>Cartwheel’s checkout reserves stock before charging a card, then releases the reservation if anything fails. The first version of that code looked like this:</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=\"Copy code\"><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>Copy</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>A declined card skips the last line, and the stock stays locked until a nightly job notices. The traditional fix is <code dir=\"ltr\">try</code>/<code dir=\"ltr\">finally</code>, nested once per resource. This lesson covers the language’s built-in answer, and then a second modern feature that keeps cross-cutting code like retries out of your business logic: standard decorators.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">using: cleanup tied to scope</h2>\n<p>A <code dir=\"ltr\">using</code> declaration is like <code dir=\"ltr\">const</code>, with one addition: when the block it lives in exits, by <code dir=\"ltr\">return</code>, by falling off the end, or by <code dir=\"ltr\">throw</code>, JavaScript calls the value’s <code dir=\"ltr\">[Symbol.dispose]()</code> method.</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=\"Copy code\"><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>Copy</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>Several <code dir=\"ltr\">using</code> declarations are disposed in reverse order, exactly like nested <code dir=\"ltr\">finally</code> blocks, so a resource is always released before the ones it was created after. If disposal itself throws while another error is in flight, both are kept in a <code dir=\"ltr\">SuppressedError</code> rather than one silently replacing the other.</p>\n<p>For cleanup that is asynchronous, such as closing a database transaction, use <code dir=\"ltr\">await using</code> with an object that has <code dir=\"ltr\">[Symbol.asyncDispose]()</code> returning a promise. And when the number of resources is dynamic, collect them in a <code dir=\"ltr\">DisposableStack</code> (or <code dir=\"ltr\">AsyncDisposableStack</code>), which is itself disposable:</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=\"Copy code\"><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>Copy</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> arrived in TypeScript 5.2. It runs natively in Node 24, Chrome and Edge 134+ and Firefox 141+, but Safari does not support it yet (as of late 2026), so it is not Baseline. For code that ships to browsers, let TypeScript or your bundler downlevel the syntax, and make sure the runtime has <code dir=\"ltr\">Symbol.dispose</code> (a polyfill provides it where it is missing). The types live in the <code dir=\"ltr\">esnext.disposable</code> lib (included in <code dir=\"ltr\">&quot;lib&quot;: [&quot;esnext&quot;]</code>). The exercise playground uses the ES2022 lib, so its starter declares those two pieces itself, which is a preview of the declaration merging you will learn in the next lesson.</p>\n<h3 id=\"sec-2\" tabindex=\"-1\">Making your own APIs disposable</h3>\n<p><code dir=\"ltr\">using</code> pays off most when your own APIs return disposables. The emitter from <a href=\"/lesson/advanced-typescript/l-at-4-4\">Type-Safe Event Emitters and Builders</a> returned an <code dir=\"ltr\">off</code> function; returning an object with <code dir=\"ltr\">[Symbol.dispose]</code> as well lets callers scope a subscription to a block:</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=\"Copy code\"><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>Copy</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>Good candidates are anything with a matching “undo”: locks and reservations, event subscriptions, timers, temporary files, database transactions (with <code dir=\"ltr\">await using</code>). If you find yourself writing <code dir=\"ltr\">try</code>/<code dir=\"ltr\">finally</code> only to call a cleanup method, that resource wants a <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\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Forgetting that using is per block</span></p>\n<p><code dir=\"ltr\">using</code> disposes at the end of the <em>block</em>, not the function. Declare a reservation inside an <code dir=\"ltr\">if</code> or a loop body and it is released when that block ends, possibly before you charge the card. Put the <code dir=\"ltr\">using</code> in the block whose lifetime matches the resource.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">Standard decorators</h2>\n<p>A decorator is a function applied with <code dir=\"ltr\">@</code> to a class or class member. TypeScript 5.0 implemented the standard (TC39) decorators, and TypeScript compiles them down to ordinary function calls, so they run in any runtime. A method decorator receives the original method and a <code dir=\"ltr\">context</code> object, and may return a replacement:</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=\"Copy code\"><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>Copy</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>The generics make the decorator work on any method without <code dir=\"ltr\">any</code>: <code dir=\"ltr\">This</code>, <code dir=\"ltr\">Args</code> and <code dir=\"ltr\">Return</code> are inferred from the method it decorates, and the replacement keeps the same signature. Decorators that take options are <strong>decorator factories</strong>, functions that return the 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=\"Copy code\"><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>Copy</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>Stacked decorators apply from the bottom up. With <code dir=\"ltr\">@logged</code> above <code dir=\"ltr\">@retry(3)</code>, the retry wrapper is applied to <code dir=\"ltr\">charge</code> first and <code dir=\"ltr\">logged</code> wraps the result, so you get one log line per call rather than one per attempt. Swap them and you log every attempt. Neither is wrong, but the order is behaviour, so choose it on purpose.</p>\n<p><code dir=\"ltr\">context</code> also offers <code dir=\"ltr\">context.name</code>, <code dir=\"ltr\">context.static</code>, <code dir=\"ltr\">context.private</code> and <code dir=\"ltr\">context.addInitializer(fn)</code>, which runs code when an instance is created (the usual way to write a <code dir=\"ltr\">@bound</code> decorator that binds a method to its instance).</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\">Why this matters</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Standard versus legacy decorators</span></p>\n<p>Many existing frameworks (Angular, NestJS, TypeORM) were built on TypeScript’s older <code dir=\"ltr\">experimentalDecorators</code>, which use a different signature <code dir=\"ltr\">(target, key, descriptor)</code> and support parameter decorators and <code dir=\"ltr\">emitDecoratorMetadata</code>. The two systems cannot be mixed in one compilation. If a framework requires the legacy flag, follow it; for new code without such a framework, use standard decorators, and use them sparingly. A plain higher-order function, <code dir=\"ltr\">const charge = withRetry(3, rawCharge)</code>, is often clearer and needs no class.</p>\n</aside>\n<p>In the exercise you will release stock reservations with <code dir=\"ltr\">using</code> and add a typed <code dir=\"ltr\">@retry</code> to Cartwheel’s payment gateway. Next lesson: describing code you did not write, with declaration files.</p>\n","toc":[{"id":"sec-1","text":"using: cleanup tied to scope","level":2},{"id":"sec-2","text":"Making your own APIs disposable","level":3},{"id":"sec-3","text":"Standard decorators","level":2}],"takeaways":["A <code dir=\"ltr\">using</code> declaration calls the value’s <code dir=\"ltr\">[Symbol.dispose]()</code> when the enclosing block exits, whether it returns normally or throws.","Several <code dir=\"ltr\">using</code> declarations in one block are disposed in reverse order, like nested <code dir=\"ltr\">try</code>/<code dir=\"ltr\">finally</code> blocks.","<code dir=\"ltr\">await using</code> and <code dir=\"ltr\">Symbol.asyncDispose</code> do the same for cleanup that returns a promise.","Standard decorators (TypeScript 5.0+) are functions of <code dir=\"ltr\">(value, context)</code> that can return a replacement; they are not the same as the older <code dir=\"ltr\">experimentalDecorators</code>.","Type a method decorator with <code dir=\"ltr\">ClassMethodDecoratorContext</code> and generic <code dir=\"ltr\">This</code>, <code dir=\"ltr\">Args</code> and <code dir=\"ltr\">Return</code> parameters so it works on any method without <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>What does this log?</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=\"Copy code\"><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>Copy</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":"Correct. The throw exits the block, both resources are disposed, and disposal runs in reverse order of declaration."},{"text":"reserve TENT-2P, reserve MUG-SET, release TENT-2P, release MUG-SET","why":"Disposal is last-in, first-out, like nested <code dir=\"ltr\">finally</code> blocks, so <code dir=\"ltr\">b</code> is released before <code dir=\"ltr\">a</code>."},{"text":"reserve TENT-2P, reserve MUG-SET (nothing is released because the function threw)","why":"Releasing on every exit path, including throws, is the whole point of <code dir=\"ltr\">using</code>."},{"text":"Nothing; <code dir=\"ltr\">using</code> cannot be combined with <code dir=\"ltr\">throw</code> in the same block.","why":"There is no such restriction; a throw is one of the exits <code dir=\"ltr\">using</code> is designed for."}],"answer":0},{"q":"<p>Which value can be bound with <code dir=\"ltr\">using</code>?</p>","options":[{"text":"Any object with a <code dir=\"ltr\">close()</code> method.","why":"<code dir=\"ltr\">using</code> does not look for <code dir=\"ltr\">close()</code>; it calls the method stored under <code dir=\"ltr\">Symbol.dispose</code>."},{"text":"An object with a <code dir=\"ltr\">[Symbol.dispose]()</code> method, or <code dir=\"ltr\">null</code>/<code dir=\"ltr\">undefined</code>.","why":"Correct. The value must be disposable; <code dir=\"ltr\">null</code> and <code dir=\"ltr\">undefined</code> are allowed and simply skipped."},{"text":"Only instances of <code dir=\"ltr\">DisposableStack</code>.","why":"<code dir=\"ltr\">DisposableStack</code> is a helper for collecting disposables, not a requirement."}],"answer":1},{"q":"<p>How does a standard method decorator receive the method it decorates?</p>","options":[{"text":"As <code dir=\"ltr\">(target, propertyKey, descriptor)</code>, and it modifies the descriptor.","why":"That is the legacy <code dir=\"ltr\">experimentalDecorators</code> signature. Standard decorators receive <code dir=\"ltr\">(value, context)</code>."},{"text":"As a string name, and it looks the method up on <code dir=\"ltr\">this</code>.","why":"The method itself is passed in as the first argument; the name is available on <code dir=\"ltr\">context.name</code>."},{"text":"As <code dir=\"ltr\">(value, context)</code>, where <code dir=\"ltr\">value</code> is the method and the decorator may return a replacement function.","why":"Correct. Returning a new function replaces the method, which is how logging and retry decorators work."}],"answer":2},{"q":"<p>Your codebase uses NestJS-style parameter decorators like <code dir=\"ltr\">constructor(@Inject(TOKEN) svc)</code>. What does that mean for your tsconfig?</p>","options":[{"text":"Nothing; standard decorators support parameters.","why":"Standard decorators do not support parameter decorators, so this syntax is rejected without the legacy flag."},{"text":"Parameter decorators need <code dir=\"ltr\">&quot;experimentalDecorators&quot;: true</code>, which switches the whole project to the legacy decorator semantics.","why":"Correct. The two systems cannot be mixed in one compilation; frameworks built on the legacy model need the flag."},{"text":"You must set <code dir=\"ltr\">&quot;target&quot;: &quot;esnext&quot;</code> so the runtime handles them.","why":"The target does not change which decorator system TypeScript uses; the flag does."},{"text":"You must enable <code dir=\"ltr\">emitDecoratorMetadata</code> alone.","why":"That option only works together with <code dir=\"ltr\">experimentalDecorators</code>; on its own it does nothing for 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":"Release the stock, retry the charge","prompt":"<p>Two fixes for Cartwheel’s checkout:</p>\n<ol>\n<li>Make <code dir=\"ltr\">reserve</code> return a disposable (with a <code dir=\"ltr\">[Symbol.dispose]</code> method that logs <code dir=\"ltr\">release &lt;sku&gt;</code> instead of a <code dir=\"ltr\">release</code> method), and rewrite <code dir=\"ltr\">placeOrder</code> with <code dir=\"ltr\">using</code> so every reservation is released when the function exits, <strong>in reverse order</strong>, even when the charge throws.</li>\n<li>Write a typed decorator factory <code dir=\"ltr\">retry(times)</code> and apply <code dir=\"ltr\">@retry(3)</code> to <code dir=\"ltr\">PaymentGateway.charge</code>, so a charge that throws is attempted up to 3 times in total before the error propagates. Keep <code dir=\"ltr\">charge</code>’s signature intact.</li>\n</ol>\n<p>The top of the file declares <code dir=\"ltr\">Symbol.dispose</code> and <code dir=\"ltr\">Disposable</code>, because the playground uses the ES2022 lib. <code dir=\"ltr\">DisposableStack</code> is not available here; if you need one, write a small class of your own.</p>\n","hints":["<code dir=\"ltr\">using r = reserve('A');</code> releases <code dir=\"ltr\">r</code> at the end of the block. For a dynamic list of SKUs, collect the disposables in a small class with a <code dir=\"ltr\">use(item)</code> method and a <code dir=\"ltr\">[Symbol.dispose]()</code> that disposes them in reverse.","A standard method decorator has the shape <code dir=\"ltr\">(method, context: ClassMethodDecoratorContext&lt;This, …&gt;) =&gt; replacementFunction</code>. A factory is a function that returns one: <code dir=\"ltr\">function retry(times: number) { return function &lt;This, Args extends unknown[], Return&gt;(method, context) { … } }</code>.","Inside the replacement, loop: <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> ties a resource’s lifetime to a block: whatever way <code dir=\"ltr\">placeOrder</code> exits, the stack is disposed, and it disposes reservations last-in, first-out, so the starter’s leak on a declined charge is gone. The decorator factory captures <code dir=\"ltr\">times</code> and returns a standard method decorator; the generic <code dir=\"ltr\">This</code>, <code dir=\"ltr\">Args</code> and <code dir=\"ltr\">Return</code> are inferred from <code dir=\"ltr\">charge</code>, so the replacement keeps its exact signature (the <code dir=\"ltr\">T2</code> test) and calls the original with <code dir=\"ltr\">method.call(this, ...args)</code>. The retry logic now lives in one reusable place instead of inside the payment code.</p>\n","checkLabels":{"types":"The file type-checks with zero errors, including the type tests","releases":"Reservations are released after the charge, in reverse order","releases-on-error":"Reservations are released even when the charge finally fails","retries":"<code dir=\"ltr\">charge</code> succeeds on the third attempt after two failures","gives-up":"<code dir=\"ltr\">charge</code> gives up and rethrows after 3 attempts"}},"words":748,"readMinutes":4}