{"id":4201,"date":"2026-05-21T10:29:45","date_gmt":"2026-05-21T03:29:45","guid":{"rendered":"https:\/\/daiilynews.cu.ma\/?p=4201"},"modified":"2026-05-21T10:29:45","modified_gmt":"2026-05-21T03:29:45","slug":"your-trycatch-sucks-lets-fix-it","status":"publish","type":"post","link":"https:\/\/daiilynews.cu.ma\/?p=4201","title":{"rendered":"Your trycatch sucks &#8211; lets fix it"},"content":{"rendered":"<p> <br \/>\n<\/p>\n<p>You&#8217;re not handling errors. You&#8217;re hiding them.<\/p>\n<p>Every app crashes. Every API fails. Every database hiccups at 2am on a Friday.The difference between a good dev and a great one? What happens next.<\/p>\n<p>Let&#8217;s roast your error handling \u2014 then make it legendary.<\/p>\n<p>  \ud83e\udd26 Level 0: The &#8220;Trust Me Bro&#8221; Dev<\/p>\n<p>No try\/catch at all. Just vibes.<\/p>\n<p>const data = await fetchUserData(userId);<br \/>\nconsole.log(data.profile.name); \/\/ \ud83d\udca5 TypeError: Cannot read properties of undefined<\/p>\n<p>    Enter fullscreen mode<\/p>\n<p>    Exit fullscreen mode<\/p>\n<p>The crime: One bad response nukes the entire app. Users see a white screen. You get a 3am Slack ping.<\/p>\n<p>  \ud83d\udc23 Level 1: The Junior \u2014 &#8220;I Googled try\/catch&#8221;<\/p>\n<p>try {<br \/>\n  const data = await fetchUserData(userId);<br \/>\n  setUser(data);<br \/>\n} catch (err) {<br \/>\n  console.log(err); \/\/ \ud83d\udc48 and&#8230; that&#8217;s it. shipped.<br \/>\n}<\/p>\n<p>    Enter fullscreen mode<\/p>\n<p>    Exit fullscreen mode<\/p>\n<p>What&#8217;s wrong here?<\/p>\n<p>console.log in production helps nobody \u2014 users still see a broken UI<br \/>\nNo distinction between a 404 and a 500 \u2014 every error is treated the same<br \/>\nThe error disappears into the void (or a DevTools tab nobody has open)<\/p>\n<p>err might be null, a string, or an Error object \u2014 you&#8217;re not checking<\/p>\n<p>The mindset: &#8220;At least it won&#8217;t crash.&#8221; \u2014 Yeah, it just silently breaks instead. Cool.<\/p>\n<p>  \ud83d\udcc8 Level 2: The Mid-Level \u2014 &#8220;I&#8217;ve Been Burned Before&#8221;<\/p>\n<p>Now we&#8217;re thinking. You&#8217;ve seen production fires. You have trust issues with APIs. Good.<\/p>\n<p>  2a \u2014 Typed errors, real messages<\/p>\n<p>try {<br \/>\n  const data = await fetchUserData(userId);<br \/>\n  setUser(data);<br \/>\n} catch (err) {<br \/>\n  if (err instanceof NetworkError) {<br \/>\n    showToast(&#8220;Connection lost. Check your internet.&#8221;, &#8220;warning&#8221;);<br \/>\n  } else if (err.status === 404) {<br \/>\n    showToast(&#8220;User not found.&#8221;, &#8220;error&#8221;);<br \/>\n  } else {<br \/>\n    showToast(&#8220;Something went wrong. We&#8217;re on it.&#8221;, &#8220;error&#8221;);<br \/>\n    logger.error(&#8220;(fetchUserData)&#8221;, { userId, err }); \/\/ \ud83d\udc48 goes to Sentry\/Datadog<br \/>\n  }<br \/>\n}<\/p>\n<p>    Enter fullscreen mode<\/p>\n<p>    Exit fullscreen mode<\/p>\n<p>\u2705 Users get useful feedback, not a frozen screen\u2705 Engineers get structured logs, not a haystack of console.logs<\/p>\n<p>  2b \u2014 Undo previous operations (the &#8220;atomic mindset&#8221;)<\/p>\n<p>Imagine you&#8217;re updating a user&#8217;s profile and their avatar. Step 1 succeeds. Step 2 fails.Congrats \u2014 your user now has a corrupted half-state.<\/p>\n<p>let previousProfile = null;<\/p>\n<p>try {<br \/>\n  previousProfile = await getProfile(userId); \/\/ snapshot<br \/>\n  await updateProfile(userId, newProfileData); \/\/ step 1<br \/>\n  await uploadAvatar(userId, newAvatar);        \/\/ step 2 \ud83d\udca5 fails here<br \/>\n} catch (err) {<br \/>\n  logger.error(&#8220;Profile update failed&#8221;, { err });<\/p>\n<p>  \/\/ \u21a9\ufe0f Roll back step 1 since step 2 failed<br \/>\n  if (previousProfile) {<br \/>\n    await updateProfile(userId, previousProfile);<br \/>\n  }<\/p>\n<p>  showToast(&#8220;Update failed. Your profile has been restored.&#8221;, &#8220;warning&#8221;);<br \/>\n}<\/p>\n<p>    Enter fullscreen mode<\/p>\n<p>    Exit fullscreen mode<\/p>\n<p>\u2705 Users never see broken half-state\u2705 Rollback is explicit, not accidental<\/p>\n<p>  2c \u2014 Wrap it in a clean utility (stop repeating yourself)<\/p>\n<p>Tired of writing try\/catch 50 times? Make a helper:<\/p>\n<p>\/\/ utils\/tryCatch.js<br \/>\nexport async function tryCatch(fn, fallback = null) {<br \/>\n  try {<br \/>\n    const result = await fn();<br \/>\n    return (result, null);<br \/>\n  } catch (err) {<br \/>\n    return (fallback, err);<br \/>\n  }<br \/>\n}<\/p>\n<p>\/\/ Usage \u2014 clean, flat, readable<br \/>\nconst (user, err) = await tryCatch(() => fetchUserData(userId));<\/p>\n<p>if (err) {<br \/>\n  showToast(&#8220;Couldn&#8217;t load user.&#8221;, &#8220;error&#8221;);<br \/>\n  return;<br \/>\n}<\/p>\n<p>setUser(user);<\/p>\n<p>    Enter fullscreen mode<\/p>\n<p>    Exit fullscreen mode<\/p>\n<p>\u2705 No more deeply nested try\/catch pyramids\u2705 Forces you to handle the error at call site \u2014 can&#8217;t ignore it<\/p>\n<p>  \ud83e\udde0 Level 3: The Senior \u2014 &#8220;I&#8217;ve Seen Things&#8221;<\/p>\n<p>You don&#8217;t just catch errors. You anticipate them. You build systems that heal themselves.<\/p>\n<p>  3a \u2014 Retry queue with exponential backoff<\/p>\n<p>Networks are flaky. Don&#8217;t give up on the first failure.<\/p>\n<p>async function fetchWithRetry(fn, { retries = 3, delay = 500 } = {}) {<br \/>\n  for (let attempt = 1; attempt  retries; attempt++) {<br \/>\n    try {<br \/>\n      return await fn();<br \/>\n    } catch (err) {<br \/>\n      const isLast = attempt === retries;<br \/>\n      const isRetryable = err.status >= 500 || err instanceof NetworkError;<\/p>\n<p>      if (isLast || !isRetryable) throw err; \/\/ don&#8217;t retry 401s or 404s<\/p>\n<p>      const backoff = delay * 2 ** (attempt &#8211; 1); \/\/ 500ms \u2192 1s \u2192 2s<br \/>\n      logger.warn(`Attempt ${attempt} failed. Retrying in ${backoff}ms&#8230;`);<br \/>\n      await sleep(backoff);<br \/>\n    }<br \/>\n  }<br \/>\n}<\/p>\n<p>\/\/ Usage<br \/>\nconst data = await fetchWithRetry(() => fetchUserData(userId));<\/p>\n<p>    Enter fullscreen mode<\/p>\n<p>    Exit fullscreen mode<\/p>\n<p>\u2705 Temporary blips are invisible to users\u2705 Smart: retries server errors, not client errors (no point retrying a 401)<\/p>\n<p>  3b \u2014 Circuit breaker (stop hammering a dead service)<\/p>\n<p>A retry queue is great \u2014 unless the whole service is down. Then you&#8217;re just DDoS-ing a corpse.<\/p>\n<p>class CircuitBreaker {<br \/>\n  constructor(threshold = 5, timeout = 30_000) {<br \/>\n    this.failures = 0;<br \/>\n    this.threshold = threshold;<br \/>\n    this.timeout = timeout;<br \/>\n    this.state = &#8220;CLOSED&#8221;; \/\/ CLOSED = healthy, OPEN = tripped, HALF_OPEN = testing<br \/>\n    this.nextAttempt = Date.now();<br \/>\n  }<\/p>\n<p>  async call(fn) {<br \/>\n    if (this.state === &#8220;OPEN&#8221;) {<br \/>\n      if (Date.now()  this.nextAttempt) {<br \/>\n        throw new Error(&#8220;Circuit open \u2014 service unavailable&#8221;);<br \/>\n      }<br \/>\n      this.state = &#8220;HALF_OPEN&#8221;;<br \/>\n    }<\/p>\n<p>    try {<br \/>\n      const result = await fn();<br \/>\n      this.reset();<br \/>\n      return result;<br \/>\n    } catch (err) {<br \/>\n      this.recordFailure();<br \/>\n      throw err;<br \/>\n    }<br \/>\n  }<\/p>\n<p>  recordFailure() {<br \/>\n    this.failures++;<br \/>\n    if (this.failures >= this.threshold) {<br \/>\n      this.state = &#8220;OPEN&#8221;;<br \/>\n      this.nextAttempt = Date.now() + this.timeout;<br \/>\n      logger.error(&#8220;\ud83d\udd34 Circuit breaker TRIPPED&#8221;);<br \/>\n    }<br \/>\n  }<\/p>\n<p>  reset() {<br \/>\n    this.failures = 0;<br \/>\n    this.state = &#8220;CLOSED&#8221;;<br \/>\n  }<br \/>\n}<\/p>\n<p>\/\/ Usage<br \/>\nconst userServiceBreaker = new CircuitBreaker();<br \/>\nconst data = await userServiceBreaker.call(() => fetchUserData(userId));<\/p>\n<p>    Enter fullscreen mode<\/p>\n<p>    Exit fullscreen mode<\/p>\n<p>\u2705 Failing fast is kind \u2014 users get an error immediately, not after 10s of retrying\u2705 Gives the downstream service a breather to recover<\/p>\n<p>  3c \u2014 Structured error classes (errors that mean something)<\/p>\n<p>Stop throwing raw strings or generic Errors. Give your errors context.<\/p>\n<p>class AppError extends Error {<br \/>\n  constructor(message, { code, statusCode = 500, context = {}, retryable = false } = {}) {<br \/>\n    super(message);<br \/>\n    this.name = &#8220;AppError&#8221;;<br \/>\n    this.code = code;<br \/>\n    this.statusCode = statusCode;<br \/>\n    this.context = context;<br \/>\n    this.retryable = retryable;<br \/>\n    this.timestamp = new Date().toISOString();<br \/>\n  }<br \/>\n}<\/p>\n<p>\/\/ Subclass for specificity<br \/>\nclass AuthError extends AppError {<br \/>\n  constructor(message, context) {<br \/>\n    super(message, { code: &#8220;AUTH_ERROR&#8221;, statusCode: 401, context, retryable: false });<br \/>\n  }<br \/>\n}<\/p>\n<p>class ServiceUnavailableError extends AppError {<br \/>\n  constructor(service, context) {<br \/>\n    super(`${service} is unavailable`, { code: &#8220;SERVICE_DOWN&#8221;, statusCode: 503, context, retryable: true });<br \/>\n  }<br \/>\n}<\/p>\n<p>\/\/ Throwing<br \/>\nthrow new ServiceUnavailableError(&#8220;UserService&#8221;, { userId, attempt: 3 });<\/p>\n<p>\/\/ Catching<br \/>\ncatch (err) {<br \/>\n  if (err instanceof AuthError) {<br \/>\n    redirectToLogin();<br \/>\n  } else if (err instanceof AppError &#038;&#038; err.retryable) {<br \/>\n    retryQueue.add(err);<br \/>\n  } else {<br \/>\n    logger.error(err.code, err.context);<br \/>\n    showToast(&#8220;Unexpected error. Engineers notified.&#8221;);<br \/>\n  }<br \/>\n}<\/p>\n<p>    Enter fullscreen mode<\/p>\n<p>    Exit fullscreen mode<\/p>\n<p>\u2705 Catch blocks can make decisions, not just log and pray\u2705 Every error carries its own context \u2014 no more guessing what happened<\/p>\n<p>  \ud83d\uddfa\ufe0f The Full Picture<\/p>\n<p>What you do<br \/>\nJunior<br \/>\nMid<br \/>\nSenior<\/p>\n<p>Catch errors<br \/>\n\u2705<br \/>\n\u2705<br \/>\n\u2705<\/p>\n<p>Inform the user<br \/>\n\u274c<br \/>\n\u2705<br \/>\n\u2705<\/p>\n<p>Send to a logger<br \/>\n\u274c<br \/>\n\u2705<br \/>\n\u2705<\/p>\n<p>Typed\/structured errors<br \/>\n\u274c<br \/>\n\u26a0\ufe0f partial<br \/>\n\u2705<\/p>\n<p>Rollback on failure<br \/>\n\u274c<br \/>\n\u2705<br \/>\n\u2705<\/p>\n<p>Retry transient errors<br \/>\n\u274c<br \/>\n\u274c<br \/>\n\u2705<\/p>\n<p>Circuit breaker<br \/>\n\u274c<br \/>\n\u274c<br \/>\n\u2705<\/p>\n<p>Errors are self-describing<br \/>\n\u274c<br \/>\n\u274c<br \/>\n\u2705<\/p>\n<p>  \u2705 The Golden Rules<\/p>\n<p>Never swallow errors silently. A hidden bug is a time bomb.<\/p>\n<p>Always tell the user something. Frozen UI is worse than an error message.<\/p>\n<p>Log with context, not just a message \u2014 what failed, who triggered it, when.<\/p>\n<p>Not all errors are equal \u2014 404 \u2260 500 \u2260 NetworkError. Handle them differently.<\/p>\n<p>Retryable \u2260 always retry \u2014 client errors (4xx) should fail fast.<\/p>\n<p>Leave the system in a valid state. Roll back or compensate when operations are partial.<\/p>\n<p>Your catch block is business logic \u2014 treat it that way.<\/p>\n<p>&#8220;The mark of a great engineer isn&#8217;t writing code that never fails.It&#8217;s writing code that fails gracefully.&#8221;<\/p>\n<p>Now go fix your try\/catches. \ud83d\udee0\ufe0f<\/p>\n<p><br \/>\n<br \/><a href=\"https:\/\/dev.to\/0shuvo0\/your-trycatch-sucks-lets-fix-it-37e9\">Source link <\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>You&#8217;re not handling errors. You&#8217;re hiding them. Every app crashes. Every API fails. Every database hiccups at 2am on a Friday.The difference between a good dev and a great one? What happens next. Let&#8217;s roast your error handling \u2014 then make it legendary. \ud83e\udd26 Level 0: The &#8220;Trust Me Bro&#8221; Dev No try\/catch at all. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":4202,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[676],"tags":[761,765,762,763,764,826,860,760,904],"class_list":["post-4201","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tech-ai","tag-coding","tag-community","tag-development","tag-engineering","tag-inclusive","tag-javascript","tag-programming","tag-software","tag-typescript"],"_links":{"self":[{"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=\/wp\/v2\/posts\/4201","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4201"}],"version-history":[{"count":0,"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=\/wp\/v2\/posts\/4201\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=\/wp\/v2\/media\/4202"}],"wp:attachment":[{"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4201"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4201"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/daiilynews.cu.ma\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4201"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}