[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"article-promise-try-synchronous-throw-safety":44,"search-suggestions":245,"related-promise-try-synchronous-throw-safety":284,"code:js:true:13rgla8":416,"code:js:true:1y5yoo8":417,"code:js:true:ubds52":418,"code:js:true:1eorztb":419,"comments-01a0764b-4844-732e-99fe-3e0122793752":420},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":53,"likeCount":19,"commentCount":19,"author":54,"vertical":59,"topic":60,"tags":63,"_count":75,"playground":77,"body":79,"bodyMd":228,"seo":229,"translationGroupId":232,"series":48,"podcastUrl":48,"verticalId":5,"thread":233,"assessments":235,"translations":241,"quiz":243},"01a0764b-4844-732e-99fe-3e0122793752","promise-try-synchronous-throw-safety","My .catch() Never Ran. The Bug Was Two Lines Above It",null,"A function that sometimes throws synchronously instead of returning a promise will skip every .catch() chained to it — because the promise never gets created. Promise.try() fixes it, and does one thing try\u002Fcatch can't.","\u002Fmedia\u002Fcovers\u002Fpromise-try-synchronous-throw-safety.png",5,"2026-09-11T10:56:58.379Z",28,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},"javascript","JavaScript",[64,66,69,72],{"slug":61,"name":65,"color":48},"Javascript",{"slug":67,"name":68,"color":48},"webdev","Webdev",{"slug":70,"name":71,"color":48},"node","Node",{"slug":73,"name":74,"color":48},"programming","Programming",{"assessments":76},1,{"slug":46,"title":78},"Promise.try() — interactive playground",{"blocks":80,"version":76},[81,85,88,94,97,102,105,108,111,114,117,120,124,127,131,134,138,141,145,148,151,154,158,161,164,167,170,173,176,179,182,185,188,191,194,197,200,203,206,209,212,215,218,221],{"id":82,"html":83,"type":84},"b1","\u003Cp>Sentry paged me at 2am for an unhandled exception in a function that had a \u003Ccode>.catch()\u003C\u002Fcode> on every single call site. I read the code three times before 2:15 and still couldn&#39;t find how it got past. The error wasn&#39;t hiding in the \u003Ccode>.catch()\u003C\u002Fcode>. It was two lines above it, in code that looked completely unremarkable.\u003C\u002Fp>","paragraph",{"id":86,"html":87,"type":84},"b2","\u003Cp>Here&#39;s the shape of it, trimmed to the part that matters:\u003C\u002Fp>",{"id":89,"code":90,"type":91,"language":92,"highlight":93},"b3","function getUser(id) {\n  if (typeof id !== \"number\") {\n    throw new TypeError(`id must be a number, got ${typeof id}`);\n  }\n  return fetch(`\u002Fapi\u002Fusers\u002F${id}`).then((r) => r.json());\n}\n\ngetUser(userId)\n  .then(renderProfile)\n  .catch(reportError);","code","js",[],{"id":95,"html":96,"type":84},"b4","\u003Cp>Somewhere upstream, \u003Ccode>userId\u003C\u002Fcode> had come through as \u003Ccode>undefined\u003C\u002Fcode>. \u003Ccode>getUser\u003C\u002Fcode> did exactly what it was written to do — it threw. And the page crashed anyway, \u003Ccode>reportError\u003C\u002Fcode> never called, like the \u003Ccode>.catch()\u003C\u002Fcode> wasn&#39;t even there.\u003C\u002Fp>",{"id":98,"html":99,"text":100,"type":101,"level":31},"b5","Before you scroll — where&#39;s the bug?","Before you scroll — where's the bug?","heading",{"id":103,"html":104,"type":84},"b6","\u003Cp>Take a second. The \u003Ccode>.catch()\u003C\u002Fcode> is right there, chained to the call. What did I miss?\u003C\u002Fp>",{"id":106,"html":107,"type":84},"b7","\u003Cp>Here&#39;s the part that took me embarrassingly long to see: \u003Ccode>getUser\u003C\u002Fcode> is a plain function, not an \u003Ccode>async\u003C\u002Fcode> one. When you write \u003Ccode>getUser(userId)\u003C\u002Fcode>, JavaScript runs the entire body of \u003Ccode>getUser\u003C\u002Fcode> \u003Cstrong>synchronously\u003C\u002Fstrong>, top to bottom, before it hands anything back to the caller. If \u003Ccode>id\u003C\u002Fcode> is bad, the \u003Ccode>throw\u003C\u002Fcode> fires during that synchronous run — before \u003Ccode>getUser\u003C\u002Fcode> has returned anything, including before it&#39;s returned a promise.\u003C\u002Fp>",{"id":109,"html":110,"type":84},"b8","\u003Cp>\u003Ccode>.then()\u003C\u002Fcode> and \u003Ccode>.catch()\u003C\u002Fcode> are methods on a promise \u003Cem>object\u003C\u002Fem>. No promise object ever got created. There was nothing for \u003Ccode>.catch()\u003C\u002Fcode> to attach to, because the line \u003Ccode>getUser(userId).then(...).catch(...)\u003C\u002Fcode> never finished evaluating \u003Ccode>getUser(userId)\u003C\u002Fcode> in the first place. The exception just does what exceptions do in synchronous code: it unwinds the call stack until something catches it. Nothing did.\u003C\u002Fp>",{"id":112,"html":113,"type":84},"b9","\u003Cp>The unsettling bit is that \u003Ccode>getUser\u003C\u002Fcode> works fine 99% of the time. When \u003Ccode>id\u003C\u002Fcode> is valid, it returns a real promise from \u003Ccode>fetch(...).then(...)\u003C\u002Fcode>, and the \u003Ccode>.catch()\u003C\u002Fcode> chained after it works exactly as expected. The bug only shows up on the one path that skips the \u003Ccode>return\u003C\u002Fcode> and throws instead — which is exactly the path most likely to be a bad input nobody tested.\u003C\u002Fp>",{"id":115,"html":116,"text":116,"type":101,"level":31},"b10","The fix everyone reaches for first",{"id":118,"html":119,"type":84},"b11","\u003Cp>The obvious patch is to wrap the \u003Cem>call site\u003C\u002Fem>:\u003C\u002Fp>",{"id":121,"code":122,"type":91,"language":92,"highlight":123},"b12","try {\n  getUser(userId).then(renderProfile).catch(reportError);\n} catch (err) {\n  reportError(err);\n}",[],{"id":125,"html":126,"type":84},"b13","\u003Cp>This works. It also means \u003Ccode>reportError\u003C\u002Fcode> now has two separate roads leading to it, and every place that calls \u003Ccode>getUser\u003C\u002Fcode> needs the same wrapper or it&#39;s back to square one. I found three other call sites that didn&#39;t have it.\u003C\u002Fp>",{"id":128,"html":129,"text":130,"type":101,"level":31},"b14","If you own the function: make it \u003Ccode>async\u003C\u002Fcode>","If you own the function: make it async",{"id":132,"html":133,"type":84},"b15","\u003Cp>The clean fix, when you can change \u003Ccode>getUser\u003C\u002Fcode> itself, is one keyword:\u003C\u002Fp>",{"id":135,"code":136,"type":91,"language":92,"highlight":137},"b16","async function getUser(id) {\n  if (typeof id !== \"number\") {\n    throw new TypeError(`id must be a number, got ${typeof id}`);\n  }\n  return fetch(`\u002Fapi\u002Fusers\u002F${id}`).then((r) => r.json());\n}",[],{"id":139,"html":140,"type":84},"b17","\u003Cp>An \u003Ccode>async\u003C\u002Fcode> function \u003Cem>always\u003C\u002Fem> returns a promise, full stop — and a synchronous \u003Ccode>throw\u003C\u002Fcode> inside it is automatically converted into a rejection of that promise instead of an exception that escapes to the caller. Now every call site&#39;s \u003Ccode>.catch()\u003C\u002Fcode> works, with zero changes at the call site. This is genuinely the right fix when the function is yours.\u003C\u002Fp>",{"id":142,"html":143,"text":144,"type":101,"level":31},"b18","When it isn&#39;t yours","When it isn't yours",{"id":146,"html":147,"type":84},"b19","\u003Cp>The case that actually bit me: \u003Ccode>getUser\u003C\u002Fcode> came from a small internal library, and I didn&#39;t want to fork it just to add \u003Ccode>async\u003C\u002Fcode>. More generally — you&#39;re wrapping a callback someone handed you, a plugin hook, an event handler — something that \u003Cem>might\u003C\u002Fem> throw synchronously, \u003Cem>might\u003C\u002Fem> return a rejected promise, and you don&#39;t control which, and you can&#39;t turn it into an \u003Ccode>async function\u003C\u002Fcode> because you don&#39;t own its source.\u003C\u002Fp>",{"id":149,"html":150,"type":84},"b20","\u003Cp>The old trick for this is \u003Ccode>Promise.resolve().then(() =&gt; fn(...args))\u003C\u002Fcode> — running \u003Ccode>fn\u003C\u002Fcode> inside a \u003Ccode>.then\u003C\u002Fcode> callback so any synchronous throw becomes a rejection instead of an escaped exception. It works. It also reads like a no-op if you don&#39;t already know the idiom, and it costs an extra microtask turn before \u003Ccode>fn\u003C\u002Fcode> even starts.\u003C\u002Fp>",{"id":152,"html":153,"type":84},"b21","\u003Cp>\u003Ccode>Promise.try()\u003C\u002Fcode> is that idiom, built in and named for what it does:\u003C\u002Fp>",{"id":155,"code":156,"type":91,"language":92,"highlight":157},"b22","Promise.try(getUser, userId)\n  .then(renderProfile)\n  .catch(reportError);",[],{"id":159,"html":160,"type":84},"b23","\u003Cp>\u003Ccode>Promise.try(fn, ...args)\u003C\u002Fcode> calls \u003Ccode>fn\u003C\u002Fcode> immediately with the given arguments and always hands back a promise: resolved with whatever \u003Ccode>fn\u003C\u002Fcode> returned, chained to it if \u003Ccode>fn\u003C\u002Fcode> returned a promise, or — the part that matters here — rejected with whatever \u003Ccode>fn\u003C\u002Fcode> threw. Now every path through \u003Ccode>getUser\u003C\u002Fcode> reaches \u003Ccode>reportError\u003C\u002Fcode> through the exact same \u003Ccode>.catch()\u003C\u002Fcode>, no \u003Ccode>try\u002Fcatch\u003C\u002Fcode> duplication, no library fork.\u003C\u002Fp>",{"id":162,"html":163,"type":84},"b24","\u003C!-- playground:start -->",{"id":165,"html":166,"text":166,"type":101,"level":31},"b25","🎮 Try it yourself",{"id":168,"html":169,"type":84},"b26","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fpromise-try-synchronous-throw-safety\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":171,"html":172,"type":84},"b27","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":174,"html":175,"type":84},"b28","\u003C!-- playground:end -->",{"id":177,"html":178,"text":178,"type":101,"level":31},"b29","The detail both old workarounds get slightly wrong",{"id":180,"html":181,"type":84},"b30","\u003Cp>Here&#39;s the one thing \u003Ccode>Promise.try\u003C\u002Fcode> does that neither \u003Ccode>try\u002Fcatch\u003C\u002Fcode> around the call site nor the \u003Ccode>Promise.resolve().then(fn)\u003C\u002Fcode> hack quite matches: \u003Cstrong>timing\u003C\u002Fstrong>.\u003C\u002Fp>",{"id":183,"html":184,"type":84},"b31","\u003Cp>\u003Ccode>Promise.resolve().then(() =&gt; fn())\u003C\u002Fcode> doesn&#39;t run \u003Ccode>fn\u003C\u002Fcode> in the same tick as the call — \u003Ccode>.then()\u003C\u002Fcode> always schedules its callback as a microtask, so \u003Ccode>fn\u003C\u002Fcode> starts running \u003Cem>after\u003C\u002Fem> the current synchronous code finishes, even on the success path. \u003Ccode>Promise.try(fn)\u003C\u002Fcode> calls \u003Ccode>fn\u003C\u002Fcode> synchronously, right when you call it — the same contract an \u003Ccode>async function\u003C\u002Fcode>&#39;s body has before its first \u003Ccode>await\u003C\u002Fcode>. If \u003Ccode>fn\u003C\u002Fcode> does something time-sensitive before it returns (kicks off a synchronous side effect, reads something off \u003Ccode>document.activeElement\u003C\u002Fcode>, starts a timer), that now happens exactly when you called it, not one microtask later. Small, and easy to not care about — until you&#39;re debugging why something that &quot;obviously&quot; runs first actually ran second.\u003C\u002Fp>",{"id":186,"html":187,"type":84},"b32","\u003Cp>\u003Ccode>Promise.try\u003C\u002Fcode> reached Baseline in 2025 and by now runs in current Chrome, Firefox, and Safari, plus Node 22 and newer, without a polyfill.\u003C\u002Fp>",{"id":189,"html":190,"text":190,"type":101,"level":31},"b33","When the old way is still fine",{"id":192,"html":193,"type":84},"b34","\u003Cp>If you own the function, keep using \u003Ccode>async\u003C\u002Fcode> — it&#39;s clearer to the next person reading it than a \u003Ccode>Promise.try\u003C\u002Fcode> wrapper at every call site. Reach for \u003Ccode>Promise.try\u003C\u002Fcode> specifically at the boundary: wrapping something you \u003Cem>didn&#39;t\u003C\u002Fem> write and can&#39;t safely assume is async-shaped.\u003C\u002Fp>",{"id":195,"html":196,"type":84},"b35","\u003C!-- quiz:start -->",{"id":198,"html":199,"text":199,"type":101,"level":31},"b36","🧠 Test yourself",{"id":201,"html":202,"type":84},"b37","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fpromise-try-synchronous-throw-safety\u002Fquiz\">Take the 7-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":204,"html":205,"type":84},"b38","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":207,"html":208,"type":84},"b39","\u003C!-- quiz:end -->",{"id":210,"html":211,"type":84},"b40","\u003Cp>The \u003Ccode>.catch()\u003C\u002Fcode> didn&#39;t fail me at 2am. I had, three months earlier, when I wrote a function that wasn&#39;t safe to chain in the first place. If you&#39;ve got five minutes, grep your own codebase for a non-\u003Ccode>async\u003C\u002Fcode> function that both \u003Ccode>return\u003C\u002Fcode>s a promise and can \u003Ccode>throw\u003C\u002Fcode> before it gets there — I&#39;d bet you find at least one. What did yours look like?\u003C\u002Fp>",{"id":213,"type":214},"b41","divider",{"id":216,"html":217,"type":84},"b42","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":219,"html":220,"type":84},"b43","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":222,"type":223,"items":224,"ordered":18},"b44","list",[225,226,227],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","Sentry paged me at 2am for an unhandled exception in a function that had a `.catch()` on every single call site. I read the code three times before 2:15 and still couldn't find how it got past. The error wasn't hiding in the `.catch()`. It was two lines above it, in code that looked completely unremarkable.\n\nHere's the shape of it, trimmed to the part that matters:\n\n```js\nfunction getUser(id) {\n  if (typeof id !== \"number\") {\n    throw new TypeError(`id must be a number, got ${typeof id}`);\n  }\n  return fetch(`\u002Fapi\u002Fusers\u002F${id}`).then((r) => r.json());\n}\n\ngetUser(userId)\n  .then(renderProfile)\n  .catch(reportError);\n```\n\nSomewhere upstream, `userId` had come through as `undefined`. `getUser` did exactly what it was written to do — it threw. And the page crashed anyway, `reportError` never called, like the `.catch()` wasn't even there.\n\n## Before you scroll — where's the bug?\n\nTake a second. The `.catch()` is right there, chained to the call. What did I miss?\n\nHere's the part that took me embarrassingly long to see: `getUser` is a plain function, not an `async` one. When you write `getUser(userId)`, JavaScript runs the entire body of `getUser` **synchronously**, top to bottom, before it hands anything back to the caller. If `id` is bad, the `throw` fires during that synchronous run — before `getUser` has returned anything, including before it's returned a promise.\n\n`.then()` and `.catch()` are methods on a promise *object*. No promise object ever got created. There was nothing for `.catch()` to attach to, because the line `getUser(userId).then(...).catch(...)` never finished evaluating `getUser(userId)` in the first place. The exception just does what exceptions do in synchronous code: it unwinds the call stack until something catches it. Nothing did.\n\nThe unsettling bit is that `getUser` works fine 99% of the time. When `id` is valid, it returns a real promise from `fetch(...).then(...)`, and the `.catch()` chained after it works exactly as expected. The bug only shows up on the one path that skips the `return` and throws instead — which is exactly the path most likely to be a bad input nobody tested.\n\n## The fix everyone reaches for first\n\nThe obvious patch is to wrap the *call site*:\n\n```js\ntry {\n  getUser(userId).then(renderProfile).catch(reportError);\n} catch (err) {\n  reportError(err);\n}\n```\n\nThis works. It also means `reportError` now has two separate roads leading to it, and every place that calls `getUser` needs the same wrapper or it's back to square one. I found three other call sites that didn't have it.\n\n## If you own the function: make it `async`\n\nThe clean fix, when you can change `getUser` itself, is one keyword:\n\n```js\nasync function getUser(id) {\n  if (typeof id !== \"number\") {\n    throw new TypeError(`id must be a number, got ${typeof id}`);\n  }\n  return fetch(`\u002Fapi\u002Fusers\u002F${id}`).then((r) => r.json());\n}\n```\n\nAn `async` function *always* returns a promise, full stop — and a synchronous `throw` inside it is automatically converted into a rejection of that promise instead of an exception that escapes to the caller. Now every call site's `.catch()` works, with zero changes at the call site. This is genuinely the right fix when the function is yours.\n\n## When it isn't yours\n\nThe case that actually bit me: `getUser` came from a small internal library, and I didn't want to fork it just to add `async`. More generally — you're wrapping a callback someone handed you, a plugin hook, an event handler — something that *might* throw synchronously, *might* return a rejected promise, and you don't control which, and you can't turn it into an `async function` because you don't own its source.\n\nThe old trick for this is `Promise.resolve().then(() => fn(...args))` — running `fn` inside a `.then` callback so any synchronous throw becomes a rejection instead of an escaped exception. It works. It also reads like a no-op if you don't already know the idiom, and it costs an extra microtask turn before `fn` even starts.\n\n`Promise.try()` is that idiom, built in and named for what it does:\n\n```js\nPromise.try(getUser, userId)\n  .then(renderProfile)\n  .catch(reportError);\n```\n\n`Promise.try(fn, ...args)` calls `fn` immediately with the given arguments and always hands back a promise: resolved with whatever `fn` returned, chained to it if `fn` returned a promise, or — the part that matters here — rejected with whatever `fn` threw. Now every path through `getUser` reaches `reportError` through the exact same `.catch()`, no `try\u002Fcatch` duplication, no library fork.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fpromise-try-synchronous-throw-safety\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## The detail both old workarounds get slightly wrong\n\nHere's the one thing `Promise.try` does that neither `try\u002Fcatch` around the call site nor the `Promise.resolve().then(fn)` hack quite matches: **timing**.\n\n`Promise.resolve().then(() => fn())` doesn't run `fn` in the same tick as the call — `.then()` always schedules its callback as a microtask, so `fn` starts running *after* the current synchronous code finishes, even on the success path. `Promise.try(fn)` calls `fn` synchronously, right when you call it — the same contract an `async function`'s body has before its first `await`. If `fn` does something time-sensitive before it returns (kicks off a synchronous side effect, reads something off `document.activeElement`, starts a timer), that now happens exactly when you called it, not one microtask later. Small, and easy to not care about — until you're debugging why something that \"obviously\" runs first actually ran second.\n\n`Promise.try` reached Baseline in 2025 and by now runs in current Chrome, Firefox, and Safari, plus Node 22 and newer, without a polyfill.\n\n## When the old way is still fine\n\nIf you own the function, keep using `async` — it's clearer to the next person reading it than a `Promise.try` wrapper at every call site. Reach for `Promise.try` specifically at the boundary: wrapping something you *didn't* write and can't safely assume is async-shaped.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 7-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fpromise-try-synchronous-throw-safety\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\nThe `.catch()` didn't fail me at 2am. I had, three months earlier, when I wrote a function that wasn't safe to chain in the first place. If you've got five minutes, grep your own codebase for a non-`async` function that both `return`s a promise and can `throw` before it gets there — I'd bet you find at least one. What did yours look like?\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":47,"canonical":230,"description":231},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fpromise-try-synchronous-throw-safety","A function that sometimes throws synchronously instead of returning a promise will skip every .catch() chained to it — because the promise never gets created. Promise.try() fixes i","01a0764b-4844-732e-99fe-419eee5c7af5",{"id":234,"locked":18},"01a0764b-4897-7080-9687-48d1c128b488",[236],{"id":237,"slug":46,"title":238,"_count":239},"01a0764b-48b0-7038-b5af-b3ef02dc73f7","Promise.try() and the .catch() that never ran",{"questions":240},7,[242],{"locale":13,"slug":46},{"id":237,"slug":46,"title":238,"_count":244,"questionCount":240},{"questions":240},[246,248,250,254,258,262,266,269,273,276,277,281],{"slug":67,"name":68,"articles":247},89,{"slug":61,"name":65,"articles":249},73,{"slug":251,"name":252,"articles":253},"frontend","Frontend",69,{"slug":255,"name":256,"articles":257},"css","Css",31,{"slug":259,"name":260,"articles":261},"tutorial","Tutorial",20,{"slug":263,"name":264,"articles":265},"typescript","Typescript",12,{"slug":267,"name":268,"articles":265},"performance","Performance",{"slug":270,"name":271,"articles":272},"react","React",10,{"slug":274,"name":275,"articles":240},"browser","Browser",{"slug":70,"name":71,"articles":240},{"slug":278,"name":279,"articles":280},"grammar","Grammar",6,{"slug":282,"name":283,"articles":51},"html","Html",{"items":285,"meta":414},[286,298,320,340,358,376,395],{"id":45,"slug":46,"title":47,"subtitle":48,"excerpt":49,"coverUrl":50,"locale":13,"readingMinutes":51,"publishedAt":52,"viewCount":287,"likeCount":19,"commentCount":19,"author":288,"vertical":289,"topic":290,"tags":291,"_count":296,"playground":297,"hasQuiz":17,"hasPlayground":17},29,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[292,293,294,295],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":70,"name":71,"color":48},{"slug":73,"name":74,"color":48},{"assessments":76},{"slug":46},{"id":299,"slug":300,"title":301,"subtitle":48,"excerpt":302,"coverUrl":303,"locale":13,"readingMinutes":280,"publishedAt":304,"viewCount":305,"likeCount":19,"commentCount":19,"author":306,"vertical":307,"topic":308,"tags":311,"_count":318,"playground":319,"hasQuiz":17,"hasPlayground":17},"01a07112-8d09-7178-b258-b899a0eb496e","webauthn-passkeys-discoverable-credentials","I Shipped 'Passwordless Login.' It Still Asked for a Username.","WebAuthn's own demo code creates a credential the browser can't find on its own — so your passkey button quietly turns into a fancier security key instead of an actual passwordless sign-in. One option fixes it.","\u002Fmedia\u002Fcovers\u002Fwebauthn-passkeys-discoverable-credentials.png","2026-09-10T10:57:59.771Z",67,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":309,"name":310},"security","Security",[312,313,314,315],{"slug":67,"name":68,"color":48},{"slug":309,"name":310,"color":48},{"slug":61,"name":65,"color":48},{"slug":316,"name":317,"color":48},"webauthn","Webauthn",{"assessments":76},{"slug":300},{"id":321,"slug":322,"title":323,"subtitle":48,"excerpt":324,"coverUrl":325,"locale":13,"readingMinutes":280,"publishedAt":326,"viewCount":327,"likeCount":19,"commentCount":19,"author":328,"vertical":329,"topic":330,"tags":331,"_count":338,"playground":339,"hasQuiz":17,"hasPlayground":17},"01a06c11-0cae-7489-8373-0e3cfee9f0fb","web-share-api-native-sharing","Those Share Buttons Are Guessing What's On My Phone","Every blog footer hardcodes the same row of share icons — X, Facebook, copy link — and none of them know what's actually installed on your phone. The Web Share API hands the job to the OS instead, and it can do something a custom row never could: share an actual file.","\u002Fmedia\u002Fcovers\u002Fweb-share-api-native-sharing.png","2026-09-09T11:00:52.000Z",140,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[332,333,334,335],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":274,"name":275,"color":48},{"slug":336,"name":337,"color":48},"apis","Apis",{"assessments":76},{"slug":322},{"id":341,"slug":342,"title":343,"subtitle":48,"excerpt":344,"coverUrl":345,"locale":13,"readingMinutes":265,"publishedAt":346,"viewCount":347,"likeCount":19,"commentCount":19,"author":348,"vertical":349,"topic":350,"tags":351,"_count":356,"playground":357,"hasQuiz":17,"hasPlayground":17},"01a018ca-67d5-7020-bce5-9816c38a7a54","weekly-web-workers","Web Workers in JavaScript: The Complete Guide","Learn JavaScript Web Workers step by step: message passing, transferable objects, error handling, and a copy-paste cheat sheet for offloading work.","\u002Fmedia\u002Fcovers\u002Fweekly-web-workers.png","2026-09-07T12:03:05.323Z",217,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[352,353,354,355],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":259,"name":260,"color":48},{"slug":267,"name":268,"color":48},{"assessments":76},{"slug":342},{"id":359,"slug":360,"title":361,"subtitle":48,"excerpt":362,"coverUrl":363,"locale":13,"readingMinutes":51,"publishedAt":364,"viewCount":365,"likeCount":19,"commentCount":19,"author":366,"vertical":367,"topic":368,"tags":369,"_count":374,"playground":375,"hasQuiz":17,"hasPlayground":17},"01a05cb5-e732-76ca-8865-14b97c64f2ba","screen-wake-lock-api-keep-screen-on","Your Recipe App Is Hiding a Silent Video","Cooking sites, workout timers, and boarding-pass pages used to autoplay an invisible, muted, looping video for one job only: stopping your phone from locking its screen. The Screen Wake Lock API replaces that hack with three lines of JavaScript — and it comes with one gotcha the docs bury.","\u002Fmedia\u002Fcovers\u002Fscreen-wake-lock-api-keep-screen-on.png","2026-09-06T10:37:22.891Z",152,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[370,371,372,373],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":274,"name":275,"color":48},{"slug":336,"name":337,"color":48},{"assessments":76},{"slug":360},{"id":377,"slug":378,"title":379,"subtitle":48,"excerpt":380,"coverUrl":381,"locale":13,"readingMinutes":382,"publishedAt":383,"viewCount":384,"likeCount":19,"commentCount":19,"author":385,"vertical":386,"topic":387,"tags":388,"_count":393,"playground":394,"hasQuiz":17,"hasPlayground":17},"01a0632c-7248-73e3-9951-e8c6eaa578c7","react-weekly-compiler-memoization","React Compiler 1.0: What useMemo You Can Delete","React Compiler 1.0 is stable. Here's the re-render model it automates, which useMemo\u002FuseCallback\u002Fmemo calls you can delete, and what still needs you.","\u002Fmedia\u002Fcovers\u002Freact-weekly-compiler-memoization.png",13,"2026-09-05T10:17:49.849Z",190,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":270,"name":271},[389,390,391,392],{"slug":270,"name":271,"color":48},{"slug":61,"name":65,"color":48},{"slug":259,"name":260,"color":48},{"slug":67,"name":68,"color":48},{"assessments":76},{"slug":378},{"id":396,"slug":397,"title":398,"subtitle":48,"excerpt":399,"coverUrl":400,"locale":13,"readingMinutes":401,"publishedAt":402,"viewCount":403,"likeCount":19,"commentCount":19,"author":404,"vertical":405,"topic":406,"tags":407,"_count":412,"playground":413,"hasQuiz":17,"hasPlayground":17},"01a057f2-68b2-7521-a373-0ea70eae6f55","page-visibility-api-tab-aware-timers","Your Timer Doesn't Know You Tabbed Away","window blur feels like the obvious way to detect an idle tab, and it's wrong in ways that only show up in production. The Page Visibility API answers the actual question — can anyone see this? — so polling, timers, and video stop wasting work the moment nobody's looking.","\u002Fmedia\u002Fcovers\u002Fpage-visibility-api-tab-aware-timers.png",4,"2026-09-05T10:17:19.010Z",162,{"id":55,"name":56,"username":57,"avatarUrl":48,"headline":58},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":61,"name":62},[408,409,410,411],{"slug":61,"name":65,"color":48},{"slug":67,"name":68,"color":48},{"slug":274,"name":275,"color":48},{"slug":267,"name":268,"color":48},{"assessments":76},{"slug":397},{"page":76,"perPage":240,"total":249,"totalPages":415},11,"\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">getUser\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>id\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">typeof\u003C\u002Fspan> id \u003Cspan class=\"shj-oper\">!==\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"number\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">throw\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">TypeError\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`id must be a number, got \u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">${\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">typeof\u003C\u002Fspan> id\u003Cspan class=\"shj-kwd\">}\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`\u002Fapi\u002Fusers\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">${\u003C\u002Fspan>id\u003Cspan class=\"shj-kwd\">}\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">then\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>r\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> r\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">json\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n\u003Cspan class=\"shj-func\">getUser\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>userId\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">then\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>renderProfile\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">catch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>reportError\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">try\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-func\">getUser\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>userId\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">then\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>renderProfile\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">catch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>reportError\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">catch\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>err\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-func\">reportError\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>err\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">async\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">function\u003C\u002Fspan> \u003Cspan class=\"shj-func\">getUser\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>id\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">if\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">typeof\u003C\u002Fspan> id \u003Cspan class=\"shj-oper\">!==\u003C\u002Fspan> \u003Cspan class=\"shj-str\">\"number\"\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">throw\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">new\u003C\u002Fspan> \u003Cspan class=\"shj-class\">TypeError\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`id must be a number, got \u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">${\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">typeof\u003C\u002Fspan> id\u003Cspan class=\"shj-kwd\">}\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-func\">fetch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`\u002Fapi\u002Fusers\u002F\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">${\u003C\u002Fspan>id\u003Cspan class=\"shj-kwd\">}\u003C\u002Fspan>\u003Cspan class=\"shj-str\">`\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">then\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>r\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">=&gt;\u003C\u002Fspan> r\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">json\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-js shj-multiline\" data-lang=\"js\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-class\">Promise\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">try\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>getUser\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan> userId\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">then\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>renderProfile\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n  \u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">catch\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>reportError\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":421},[]]