[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"playground-promise-with-resolvers":3,"verticals":17},{"slug":4,"title":5,"summary":6,"html":7,"css":8,"js":9,"config":10,"doc":13},"promise-with-resolvers","Promise.withResolvers() — interactive playground",null,"\u003Cdiv class=\"pg\">\n    \u003Ch1 class=\"pg-title\">🎮 Promise.withResolvers()\u003C\u002Fh1>\n    \u003Cp class=\"pg-sub\">\n      Create a pending promise, then settle it from a button click or a timeout —\n      completely \u003Cstrong>outside\u003C\u002Fstrong> the code that created it. That's the whole point of\n      \u003Ccode>Promise.withResolvers()\u003C\u002Fcode>: \u003Ccode>resolve\u003C\u002Fcode> and \u003Ccode>reject\u003C\u002Fcode> live in your\n      scope, ready for later.\n    \u003C\u002Fp>\n\n    \u003Cdiv class=\"banner\" id=\"banner\">\n      Your browser doesn't implement \u003Ccode>Promise.withResolvers\u003C\u002Fcode> natively — this demo falls\n      back to a tiny inline polyfill so it still runs identically. Native support is\n      \u003Cstrong>Baseline 2024\u003C\u002Fstrong> (Chrome 119+, Firefox 121+, Safari 17.4+, Node.js 22+).\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">1\u003C\u002Fspan> Settle it from outside\u003C\u002Fdiv>\n      \u003Cp class=\"hint\" style=\"margin:0 0 12px\">\n        The promise below is created once, up front. Confirm, Cancel, and the timeout all fire\n        \u003Cstrong>later\u003C\u002Fstrong>, from outside — exactly like a real \"are you sure?\" dialog or a\n        socket event.\n      \u003C\u002Fp>\n      \u003Cdiv class=\"btn-row\">\n        \u003Cbutton class=\"btn good\" id=\"resolveBtn\">✅ Confirm (resolve)\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn bad\" id=\"rejectBtn\">❌ Cancel (reject)\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn ghost\" id=\"timeoutBtn\">⏱ Simulate 3s timeout\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"btn-row\" style=\"margin-top:10px\">\n        \u003Cbutton class=\"btn sm\" id=\"newBtn\">🔄 New pending promise (reset)\u003C\u002Fbutton>\n        \u003Cspan class=\"muted mono\" id=\"countdown\">\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"presets\" style=\"margin-top:14px\">\n        \u003Cbutton class=\"btn sm\" data-preset=\"confirm\">Preset: user confirms\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn sm\" data-preset=\"cancel\">Preset: user cancels\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn sm\" data-preset=\"timeout\">Preset: nobody answers\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn sm\" data-preset=\"double\">Preset: confirm, then cancel (no-op)\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">2\u003C\u002Fspan> Promise state\u003C\u002Fdiv>\n      \u003Cdiv class=\"status\" id=\"status\" aria-live=\"polite\">\n        \u003Cspan class=\"dot info\" id=\"dot\">\u003C\u002Fspan\n        >\u003Cspan id=\"statusText\">pending — waiting for an external event…\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"output\" style=\"margin-top:10px\">\n        \u003Cdiv class=\"ctl-label\" style=\"margin-bottom:6px\">Settled value\u003C\u002Fdiv>\n        \u003Cdiv class=\"mono\" id=\"value\">—\u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"log\" id=\"log\" style=\"margin-top:10px\">\u003C\u002Fdiv>\n      \u003Cp class=\"hint\">\n        \u003Ccode>resolve\u003C\u002Fcode> and \u003Ccode>reject\u003C\u002Fcode> are ordinary functions sitting in this scope —\n        call either one from anywhere, anytime. Once the promise settles, every later call is\n        silently ignored: \u003Cstrong>the first settlement always wins\u003C\u002Fstrong>. Try the last preset,\n        or click Confirm and then Cancel yourself, to see it happen.\n      \u003C\u002Fp>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">3\u003C\u002Fspan> Old way vs new way\u003C\u002Fdiv>\n      \u003Cdiv class=\"compare\">\n        \u003Cdiv class=\"compare-col\">\n          \u003Cdiv class=\"compare-head\">\u003Cspan class=\"pill bad\">escape the executor\u003C\u002Fspan>\u003C\u002Fdiv>\n          \u003Cpre class=\"code\">\u003Cspan class=\"tok-kw\">let\u003C\u002Fspan> resolve, reject;\n\u003Cspan class=\"tok-kw\">const\u003C\u002Fspan> promise = \u003Cspan class=\"tok-kw\">new\u003C\u002Fspan> \u003Cspan class=\"tok-sel\">Promise\u003C\u002Fspan>((res, rej) => {\n  resolve = res;\n  reject = rej;\n});\u003C\u002Fpre>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"compare-col\">\n          \u003Cdiv class=\"compare-head\">\u003Cspan class=\"pill good\">Promise.withResolvers()\u003C\u002Fspan>\u003C\u002Fdiv>\n          \u003Cpre class=\"code\">\u003Cspan class=\"tok-kw\">const\u003C\u002Fspan> { promise, resolve, reject } =\n  \u003Cspan class=\"hl\">\u003Cspan class=\"tok-sel\">Promise\u003C\u002Fspan>.withResolvers()\u003C\u002Fspan>;\u003C\u002Fpre>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n      \u003Cp class=\"hint\">\n        Both give you the same three things — a promise plus its resolve\u002Freject functions, free to\n        hand to code outside the constructor. The right side just skips the manual \"declare outer\n        variables, then capture them\" dance. This demo's buttons call the right-hand form (or the\n        polyfill below, if your browser lacks it).\n      \u003C\u002Fp>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>","\u002F*\n * Shared playground design system — daily-post\n * ============================================\n * Link it from any post's playground\u002Findex.html (no per-post boilerplate):\n *\n *   \u003Clink rel=\"stylesheet\" href=\"..\u002F..\u002F..\u002Fplayground\u002Fassets\u002Fbase.css\" \u002F>\n *\n * The relative path resolves the same locally (posts\u002F\u003Cbase>\u002Fplayground\u002F) and on\n * the deployed host (public\u002Fposts\u002F\u003Cbase>\u002Fplayground\u002F), because build-site.js\n * mirrors this file to public\u002Fplayground\u002Fassets\u002Fbase.css. For a CodeSandbox\n * --embed, playground.js inlines this file so the single-file sandbox still styles.\n *\n * Then write ONLY the demo-specific CSS in your inline \u003Cstyle>. Everything below\n * — tokens, layout, panels, buttons, toggles, sliders, code blocks, status\u002Flog,\n * banners, presets — comes for free and looks consistent across every post.\n *\n * Theme-aware: dark by default, light via prefers-color-scheme AND a\n * [data-theme] override on \u003Chtml> (so a toggle can force either). Accessible:\n * real focus rings, adequate contrast, reduced-motion honored.\n *\u002F\n\n\u002F* ---------- design tokens ---------- *\u002F\n:root {\n  --bg: #0d1117;\n  --bg-grid: #ffffff0a;\n  --panel: #161b22;\n  --panel-2: #0b0e14;\n  --card: #0b0e14;\n  --ink: #e8ebf2;\n  --muted: #9aa4b2;\n  --faint: #6b7688;\n  --line: #232a35;\n  --line-soft: #ffffff12;\n  --accent: #7c9cff;\n  --accent-ink: #0b0e14;\n  --good: #3fb950;\n  --warn: #f0883e;\n  --bad: #f85149;\n  --info: #58a6ff;\n  --radius: 14px;\n  --radius-sm: 9px;\n  --shadow: 0 8px 30px #0006;\n  --shadow-sm: 0 4px 14px #0005;\n  --mono: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n  --sans: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", sans-serif;\n  color-scheme: dark;\n}\n\n\u002F* Light theme — system preference, overridable by [data-theme] on \u003Chtml>. *\u002F\n@media (prefers-color-scheme: light) {\n  :root:not([data-theme=\"dark\"]) {\n    --bg: #f4f6fb;\n    --bg-grid: #00000008;\n    --panel: #ffffff;\n    --panel-2: #f0f3f9;\n    --card: #f7f9fc;\n    --ink: #131722;\n    --muted: #5b6472;\n    --faint: #8a93a3;\n    --line: #e2e7f0;\n    --line-soft: #00000010;\n    --accent: #2f6df0;\n    --accent-ink: #ffffff;\n    --good: #1a7f37;\n    --warn: #bc4c00;\n    --bad: #cf222e;\n    --info: #0969da;\n    --shadow: 0 8px 30px #0000001f;\n    --shadow-sm: 0 4px 14px #00000014;\n    color-scheme: light;\n  }\n}\n:root[data-theme=\"light\"] {\n  --bg: #f4f6fb;\n  --bg-grid: #00000008;\n  --panel: #ffffff;\n  --panel-2: #f0f3f9;\n  --card: #f7f9fc;\n  --ink: #131722;\n  --muted: #5b6472;\n  --faint: #8a93a3;\n  --line: #e2e7f0;\n  --line-soft: #00000010;\n  --accent: #2f6df0;\n  --accent-ink: #ffffff;\n  --good: #1a7f37;\n  --warn: #bc4c00;\n  --bad: #cf222e;\n  --info: #0969da;\n  --shadow: 0 8px 30px #0000001f;\n  --shadow-sm: 0 4px 14px #00000014;\n  color-scheme: light;\n}\n\n\u002F* ---------- reset & base ---------- *\u002F\n*, *::before, *::after { box-sizing: border-box; }\n\nbody {\n  margin: 0;\n  min-height: 100vh;\n  font-family: var(--sans);\n  color: var(--ink);\n  background:\n    radial-gradient(circle at 1px 1px, var(--bg-grid) 1px, transparent 0) 0 0 \u002F 24px 24px,\n    var(--bg);\n  display: flex;\n  justify-content: center;\n  align-items: flex-start;\n  padding: clamp(14px, 3vw, 26px);\n  line-height: 1.55;\n  -webkit-font-smoothing: antialiased;\n  text-rendering: optimizeLegibility;\n}\n\n\u002F* The single content column. *\u002F\n.pg {\n  width: 100%;\n  max-width: 860px;\n}\n\n\u002F* ---------- header ---------- *\u002F\n.pg-title {\n  font-size: clamp(1.15rem, 1rem + 1vw, 1.4rem);\n  font-weight: 800;\n  letter-spacing: -0.02em;\n  margin: 0 0 4px;\n  display: flex;\n  align-items: center;\n  gap: 9px;\n}\n.pg-sub {\n  color: var(--muted);\n  font-size: 0.92rem;\n  line-height: 1.5;\n  margin: 0 0 16px;\n  max-width: 66ch;\n}\n.pg-sub strong, .pg-sub b { color: var(--ink); font-weight: 700; }\n.pg-sub code { font-family: var(--mono); background: var(--line-soft); color: var(--accent);\n  padding: 1px 6px; border-radius: 6px; font-size: 0.85em; }\n\n\u002F* ---------- panels ---------- *\u002F\n.panel {\n  background: var(--panel);\n  border: 1px solid var(--line);\n  border-radius: var(--radius);\n  padding: 16px 18px;\n  margin-bottom: 14px;\n  box-shadow: var(--shadow-sm);\n}\n.panel-label {\n  font-size: 0.7rem;\n  text-transform: uppercase;\n  letter-spacing: 0.08em;\n  color: var(--muted);\n  font-weight: 800;\n  margin: 0 0 13px;\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n.panel-label .n {\n  display: inline-grid; place-items: center;\n  width: 18px; height: 18px; border-radius: 50%;\n  background: var(--accent); color: var(--accent-ink);\n  font-size: 0.66rem; font-weight: 800;\n}\n\n\u002F* ---------- layout helpers ---------- *\u002F\n.row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }\n.row.between { justify-content: space-between; }\n.col { display: flex; flex-direction: column; gap: 10px; }\n.grow { flex: 1 1 0; min-width: 0; }\n.wrap-controls { display: flex; flex-wrap: wrap; gap: 16px 24px; align-items: flex-end; }\n.field { display: flex; flex-direction: column; gap: 7px; min-width: 0; }\n.field > .label, .ctl-label {\n  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em;\n  color: var(--muted); font-weight: 800;\n}\n\n\u002F* ---------- buttons ---------- *\u002F\n.btn {\n  font: inherit; font-weight: 650; font-size: 0.88rem;\n  cursor: pointer; user-select: none;\n  border: 1px solid var(--line);\n  background: var(--card); color: var(--ink);\n  padding: 9px 15px; border-radius: var(--radius-sm);\n  display: inline-flex; align-items: center; gap: 8px;\n  transition: background .15s ease, border-color .15s ease, transform .05s ease, color .15s ease;\n}\n.btn:hover { background: var(--line-soft); border-color: var(--muted); }\n.btn:active { transform: translateY(1px); }\n.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n.btn[disabled] { opacity: 0.45; cursor: not-allowed; }\n.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }\n.btn.primary:hover { filter: brightness(1.08); background: var(--accent); }\n.btn.good { background: var(--good); border-color: var(--good); color: #04140a; }\n.btn.bad  { background: var(--bad);  border-color: var(--bad);  color: #fff; }\n.btn.ghost { background: transparent; }\n.btn.sm { padding: 6px 11px; font-size: 0.82rem; }\n\n.btn-row { display: flex; flex-wrap: wrap; gap: 9px; }\n\n\u002F* Segmented control — a row of buttons where one is active (aria-pressed). *\u002F\n.seg { display: inline-flex; background: var(--card); border: 1px solid var(--line);\n  border-radius: var(--radius-sm); padding: 3px; gap: 3px; }\n.seg button {\n  font: inherit; font-weight: 650; font-size: 0.84rem; cursor: pointer;\n  border: none; background: transparent; color: var(--muted);\n  padding: 7px 13px; border-radius: 7px;\n  transition: background .15s ease, color .15s ease;\n}\n.seg button:hover { color: var(--ink); }\n.seg button[aria-pressed=\"true\"] { background: var(--accent); color: var(--accent-ink); }\n.seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n\n\u002F* Preset chips — worked-example shortcuts. *\u002F\n.presets { display: flex; flex-wrap: wrap; gap: 8px; }\n.presets .btn { background: var(--panel-2); }\n\n\u002F* ---------- toggle switch ---------- *\u002F\n.toggle { display: inline-flex; align-items: center; gap: 10px; cursor: pointer;\n  font-size: 0.88rem; font-weight: 600; user-select: none; }\n.toggle input { position: absolute; opacity: 0; width: 0; height: 0; }\n.switch { position: relative; width: 44px; height: 25px; border-radius: 999px; flex: none;\n  background: var(--line); border: 1px solid var(--line); transition: background .18s ease; }\n.switch::after { content: \"\"; position: absolute; top: 2px; left: 2px; width: 19px; height: 19px;\n  border-radius: 50%; background: #cfd6e4; transition: transform .18s ease; box-shadow: 0 1px 3px #0006; }\n.toggle input:checked + .switch { background: var(--good); border-color: var(--good); }\n.toggle input:checked + .switch::after { transform: translateX(19px); background: #fff; }\n.toggle input:focus-visible + .switch { outline: 2px solid var(--accent); outline-offset: 2px; }\n\n\u002F* ---------- range slider ---------- *\u002F\ninput[type=\"range\"] {\n  -webkit-appearance: none; appearance: none; width: 100%; height: 6px;\n  background: var(--line); border-radius: 999px; outline: none; cursor: pointer; margin: 8px 0;\n}\ninput[type=\"range\"]::-webkit-slider-thumb {\n  -webkit-appearance: none; appearance: none; width: 19px; height: 19px; border-radius: 50%;\n  background: var(--accent); border: 2px solid var(--panel); box-shadow: var(--shadow-sm); cursor: pointer;\n}\ninput[type=\"range\"]::-moz-range-thumb {\n  width: 19px; height: 19px; border-radius: 50%; background: var(--accent);\n  border: 2px solid var(--panel); box-shadow: var(--shadow-sm); cursor: pointer;\n}\ninput[type=\"range\"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }\n\n\u002F* ---------- text inputs \u002F selects ---------- *\u002F\ninput[type=\"text\"], input[type=\"number\"], input[type=\"search\"], select, textarea {\n  font: inherit; color: var(--ink); background: var(--card);\n  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 8px 11px; width: 100%;\n}\ntextarea { font-family: var(--mono); font-size: 0.85rem; line-height: 1.6; resize: vertical; }\ninput:focus-visible, select:focus-visible, textarea:focus-visible {\n  outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent);\n}\n\n\u002F* ---------- status \u002F output \u002F log ---------- *\u002F\n.status {\n  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;\n  font-size: 0.9rem; font-weight: 600;\n  padding: 11px 14px; border-radius: var(--radius-sm);\n  background: var(--panel-2); border: 1px solid var(--line); color: var(--ink);\n}\n.status.good { border-color: color-mix(in srgb, var(--good) 55%, var(--line)); }\n.status.warn { border-color: color-mix(in srgb, var(--warn) 55%, var(--line)); }\n.status.bad  { border-color: color-mix(in srgb, var(--bad) 55%, var(--line)); }\n.dot { width: 9px; height: 9px; border-radius: 50%; background: var(--muted); flex: none; }\n.dot.good { background: var(--good); } .dot.warn { background: var(--warn); }\n.dot.bad { background: var(--bad); } .dot.info { background: var(--info); }\n\n.output { background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--radius-sm);\n  padding: 13px 15px; }\n.log { font-family: var(--mono); font-size: 0.8rem; line-height: 1.7; background: var(--card);\n  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 11px 14px;\n  max-height: 220px; overflow-y: auto; white-space: pre-wrap; }\n\n\u002F* ---------- code block + syntax tokens ---------- *\u002F\n.code {\n  font-family: var(--mono); font-size: 0.82rem; line-height: 1.7;\n  background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--radius-sm);\n  padding: 13px 15px; overflow-x: auto; white-space: pre; tab-size: 2;\n}\n.code .tok-sel  { color: var(--accent); }\n.code .tok-prop { color: #86e29b; }\n.code .tok-kw   { color: #ff9bcb; }\n.code .tok-str  { color: #f0c674; }\n.code .tok-num  { color: #f0883e; }\n.code .tok-cmt  { color: var(--faint); font-style: italic; }\n.code .hl { background: color-mix(in srgb, var(--good) 22%, transparent); border-radius: 4px; padding: 0 3px; }\n\n\u002F* ---------- hint \u002F caption ---------- *\u002F\n.hint { font-size: 0.83rem; color: var(--muted); line-height: 1.55; margin: 10px 0 0; }\n.hint code { font-family: var(--mono); color: var(--ink); background: var(--line-soft);\n  padding: 1px 5px; border-radius: 5px; font-size: 0.86em; }\n.hint strong { color: var(--ink); }\n\n\u002F* ---------- banner (feature-support \u002F notices) ---------- *\u002F\n.banner {\n  display: none; margin-bottom: 14px; padding: 11px 14px; border-radius: 11px;\n  font-size: 0.85rem; line-height: 1.5;\n  color: var(--ink); background: color-mix(in srgb, var(--warn) 12%, var(--panel));\n  border: 1px solid color-mix(in srgb, var(--warn) 45%, var(--line));\n}\n.banner.show { display: block; }\n.banner.info { background: color-mix(in srgb, var(--info) 12%, var(--panel));\n  border-color: color-mix(in srgb, var(--info) 45%, var(--line)); }\n.banner code { font-family: var(--mono); background: var(--line-soft); padding: 1px 5px; border-radius: 5px; }\n\n\u002F* ---------- misc atoms ---------- *\u002F\n.pill { display: inline-flex; align-items: center; gap: 6px; font-size: 0.75rem; font-weight: 700;\n  padding: 3px 9px; border-radius: 999px; background: var(--line-soft); color: var(--muted); }\n.pill.good { color: var(--good); background: color-mix(in srgb, var(--good) 16%, transparent); }\n.pill.bad  { color: var(--bad);  background: color-mix(in srgb, var(--bad) 16%, transparent); }\n.mono { font-family: var(--mono); }\n.muted { color: var(--muted); }\n.big-num { font-family: var(--mono); font-size: 1.6rem; font-weight: 800; letter-spacing: -0.02em; }\n\n\u002F* Optional theme toggle button (top-right). Wire it in JS if you want it. *\u002F\n.theme-btn { position: fixed; top: 12px; right: 12px; z-index: 20; }\n\n\u002F* ---------- responsive ---------- *\u002F\n@media (max-width: 560px) {\n  .panel { padding: 14px; }\n  .wrap-controls { gap: 14px; }\n  .btn { padding: 9px 13px; }\n}\n\n\u002F* ---------- reduced motion ---------- *\u002F\n@media (prefers-reduced-motion: reduce) {\n  *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }\n}\n\n\n\u002F* Demo-specific CSS ONLY. base.css already provides tokens, layout, panels,\n     buttons, toggles, sliders, code blocks, status\u002Flog, banners, presets. *\u002F\n  .compare {\n    display: grid;\n    grid-template-columns: 1fr 1fr;\n    gap: 12px;\n    align-items: start;\n  }\n  @media (max-width: 620px) {\n    .compare { grid-template-columns: 1fr; }\n  }\n  .compare-head {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    margin-bottom: 8px;\n  }\n  .compare .code { min-height: 108px; }","\"use strict\";\nconst $ = (id) => document.getElementById(id);\n\nconst hasNative = typeof Promise.withResolvers === \"function\";\nif (!hasNative) $(\"banner\").classList.add(\"show\");\n\n\u002F\u002F Tiny inline polyfill: identical shape, so the rest of the demo never has to\n\u002F\u002F care which one it's running on.\nfunction withResolvers() {\n  if (hasNative) return Promise.withResolvers();\n  let resolve, reject;\n  const promise = new Promise((res, rej) => {\n    resolve = res;\n    reject = rej;\n  });\n  return { promise, resolve, reject };\n}\n\nlet deferred = null;\nlet settled = false;\nlet settledAs = null; \u002F\u002F 'fulfilled' | 'rejected'\nlet timeoutId = null;\nlet seq = 0;\n\nfunction log(msg) {\n  const el = $(\"log\");\n  const time = new Date().toLocaleTimeString([], { hour12: false });\n  el.textContent += (el.textContent ? \"\\n\" : \"\") + `[${time}] ${msg}`;\n  el.scrollTop = el.scrollHeight;\n}\n\nfunction setStatus(text, kind) {\n  $(\"statusText\").textContent = text;\n  $(\"dot\").className = \"dot \" + kind;\n  $(\"status\").className = \"status\" + (kind === \"good\" || kind === \"bad\" ? \" \" + kind : \"\");\n}\n\nfunction clearPendingTimeout() {\n  if (timeoutId) {\n    clearTimeout(timeoutId);\n    timeoutId = null;\n  }\n  $(\"countdown\").textContent = \"\";\n}\n\nfunction newPromise() {\n  clearPendingTimeout();\n  seq += 1;\n  settled = false;\n  settledAs = null;\n  deferred = withResolvers();\n  $(\"value\").textContent = \"—\";\n  setStatus(\"pending — waiting for an external event…\", \"info\");\n  log(`#${seq} created a new pending promise via ${hasNative ? \"Promise.withResolvers()\" : \"the polyfill\"}.`);\n\n  deferred.promise.then(\n    (value) => {\n      settled = true;\n      settledAs = \"fulfilled\";\n      $(\"value\").textContent = JSON.stringify(value);\n      setStatus(`fulfilled — settled with ${JSON.stringify(value)}`, \"good\");\n      log(`#${seq} promise fulfilled with ${JSON.stringify(value)}.`);\n    },\n    (reason) => {\n      settled = true;\n      settledAs = \"rejected\";\n      $(\"value\").textContent = JSON.stringify(reason);\n      setStatus(`rejected — settled with ${JSON.stringify(reason)}`, \"bad\");\n      log(`#${seq} promise rejected with ${JSON.stringify(reason)}.`);\n    }\n  );\n}\n\nfunction doResolve(value) {\n  if (settled) {\n    log(`#${seq} resolve(${JSON.stringify(value)}) called again — ignored, already settled to ${settledAs}. First settlement wins.`);\n    return;\n  }\n  log(`#${seq} external event: Confirm clicked → resolve(${JSON.stringify(value)}) called from OUTSIDE the executor.`);\n  deferred.resolve(value);\n  clearPendingTimeout();\n}\n\nfunction doReject(reason) {\n  if (settled) {\n    log(`#${seq} reject(${JSON.stringify(reason)}) called again — ignored, already settled to ${settledAs}. First settlement wins.`);\n    return;\n  }\n  log(`#${seq} external event: Cancel clicked → reject(${JSON.stringify(reason)}) called from OUTSIDE the executor.`);\n  deferred.reject(reason);\n  clearPendingTimeout();\n}\n\n$(\"resolveBtn\").addEventListener(\"click\", () => doResolve(\"confirmed by user\"));\n$(\"rejectBtn\").addEventListener(\"click\", () => doReject(\"cancelled by user\"));\n\n$(\"timeoutBtn\").addEventListener(\"click\", () => {\n  if (settled) {\n    log(`#${seq} timeout not started — the promise already settled to ${settledAs}.`);\n    return;\n  }\n  clearPendingTimeout();\n  let secs = 3;\n  $(\"countdown\").textContent = `auto-rejects in ${secs}s…`;\n  log(`#${seq} started a 3s timeout — if nobody decides, the promise auto-rejects.`);\n  const tick = () => {\n    secs -= 1;\n    if (secs \u003C= 0) {\n      $(\"countdown\").textContent = \"\";\n      doReject(\"timed out waiting for a decision\");\n      return;\n    }\n    $(\"countdown\").textContent = `auto-rejects in ${secs}s…`;\n    timeoutId = setTimeout(tick, 1000);\n  };\n  timeoutId = setTimeout(tick, 1000);\n});\n\n$(\"newBtn\").addEventListener(\"click\", () => newPromise());\n\nconst PRESETS = {\n  confirm: () => {\n    newPromise();\n    setTimeout(() => doResolve(\"confirmed by user\"), 700);\n  },\n  cancel: () => {\n    newPromise();\n    setTimeout(() => doReject(\"cancelled by user\"), 700);\n  },\n  timeout: () => {\n    newPromise();\n    setTimeout(() => $(\"timeoutBtn\").click(), 400);\n  },\n  double: () => {\n    newPromise();\n    setTimeout(() => doResolve(\"confirmed by user\"), 500);\n    setTimeout(() => doReject(\"cancelled by user\"), 1300);\n  },\n};\ndocument.querySelectorAll(\"[data-preset]\").forEach((btn) => {\n  btn.addEventListener(\"click\", () => {\n    const fn = PRESETS[btn.dataset.preset];\n    if (fn) fn();\n  });\n});\n\nnewPromise();",{"height":11,"autorun":12},520,true,{"slug":4,"title":14,"excerpt":15,"coverUrl":16},"You keep escaping the Promise constructor. `Promise.withResolvers` does it right.","The 'deferred promise' pattern — storing resolve and reject outside the constructor so you can call them later — is a daily JavaScript idiom that's always felt awkward. ES2024 added a one-liner for it.","\u002Fmedia\u002Fcovers\u002Fpromise-with-resolvers.png",[18,33],{"id":19,"slug":20,"name":21,"tagline":22,"description":23,"accentFrom":24,"accentTo":25,"icon":26,"defaultLocale":27,"locales":28,"features":30,"position":32},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Build. Learn. Ship.","Practical courses, engineering-grade articles and open-source tools for people who ship.","violet-500","cyan-400","◇","en",[27,29],"fa",{"courses":12,"paths":12,"articles":12,"exams":31,"flashcards":31,"packages":12,"community":12,"certificates":12,"teams":12,"commerce":12},false,0,{"id":34,"slug":35,"name":36,"tagline":37,"description":38,"accentFrom":39,"accentTo":24,"icon":40,"defaultLocale":27,"locales":41,"features":43,"position":44},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Structured paths, listening drills and spaced repetition that actually sticks.","amber-400","⌘",[27,29,42],"es",{"courses":12,"paths":12,"articles":31,"exams":31,"flashcards":31,"packages":31,"community":12,"certificates":31,"teams":31,"commerce":12},2]