service-worker-router icon indicating copy to clipboard operation
service-worker-router copied to clipboard

Error Handler

Open davidbarratt opened this issue 6 years ago • 2 comments

Is there a way to define an error handler? My handler may throw an exception and it would be nice to catch the exceptions from the handlers and handle them together.

davidbarratt avatar Aug 11 '19 18:08 davidbarratt

Hey @davidbarratt - it's possible to do that, although a bit verbose (I might add a convenience wrapper for this in the future 😄)

Here's a full example (Cloudflare Worker, TypeScript) how one might do NotFound and error handling with the router:

import CloudflareWorkerGlobalScope from "types-cloudflare-worker"
declare var self: CloudflareWorkerGlobalScope

import { HandlerContext, Router } from "service-worker-router"

const ping = async () => new Response("pong")
const notFound = async () => new Response("Not found :(", { status: 404 })
const errorResponse = async (err: Error) => new Response(`Error occured: ${err.message}`)

const conditionalThrow = async ({ request, params }: HandlerContext): Promise<Response> => {
  if (params.wish === "yes") {
    throw new Error("I threw.")
  }
  return new Response("All good.", { status: 200 })
}

const router = new Router()
router.all("/_ping", ping)
router.all("/throw/:wish", conditionalThrow)

async function fetchEventHandler(event: FetchEvent): Promise<Response> {
  try {
    const match = router.handleRequest(event.request)
    if (match) {
      return await match.handlerPromise // await is important here for error catching to work
    }
    return notFound()
  } catch (err) {
    return errorResponse(err)
  }
}

self.addEventListener("fetch", (event: FetchEvent) => {
  event.respondWith(fetchEventHandler(event))
})

Results in:

❯ curl 'https://my-worker.com/_ping'
pong
❯ curl 'https://my-worker.com/foobar'
Not found :(
❯ curl 'https://my-worker.com/throw/nope'
All good.
❯ curl 'https://my-worker.com/throw/yes'
Error occured: I threw.

berstend avatar Oct 27 '19 10:10 berstend

Hi,

I've been using this router and have had a similar wish: being able to easily handle errors, here the solution I came up with:

// withErrorHandler.js
/**
 * Higher order function providing generic error handling for request handlers
 * @param {Function} func a request handler
 * @returns {Response}
 */
export function withErrorHandler (func) {
  return async function handler (...args) {
    try {
      // await is not superfluous here as otherwise the catch is bypassed
      return await func(...args)
    } catch (error) {
      const url = new URL(args.request.url)
      const debug = url.searchParams.get('debug') === 'true'
      return debug // show the stack trace, could be used to send it to a monitoring system or else
        ? new Response(error.stack || error)
        : new Response(null, { status: 500 })
    }
  }
}
// index.js
import { Router } from 'service-worker-router'
import { routeHandler } from './routeHandler'
import { withErrorHandler } from './withErrorHandler'

const router = new Router()
router.get('/path/to/handle', withErrorHandler(routeHandler))

addEventListener('fetch', (event) => {
  // Will test event.request against the defined routes
  // and use event.respondWith(handler) when a route matches
  return router.handleEvent(event)
})

Just sharing it in case it may help someone else. Note that this version doesn't handle 404's in case the router doesn't match any route.

p-j avatar Dec 05 '19 12:12 p-j