Skip to content

Commit

Permalink
Add setCatchHandler option for routes (#2699)
Browse files Browse the repository at this point in the history
* Add setCatchHandler option for routes

Resolves #2318

* Check for catchHandler in Router catch

* Update packages/workbox-routing/src/Router.ts

Co-authored-by: Jeffrey Posnick <[email protected]>

Co-authored-by: Jeffrey Posnick <[email protected]>
  • Loading branch information
Snugug and jeffposnick authored Jan 5, 2021
1 parent 72335ab commit 1e8166e
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 11 deletions.
10 changes: 10 additions & 0 deletions packages/workbox-routing/src/Route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Route {
handler: RouteHandlerObject;
match: RouteMatchCallback;
method: HTTPMethod;
catchHandler?: RouteHandlerObject;

/**
* Constructor for Route class.
Expand Down Expand Up @@ -62,6 +63,15 @@ class Route {
this.match = match;
this.method = method;
}

/**
*
* @param {module:workbox-routing-handlerCallback} handler A callback
* function that returns a Promise resolving to a Response
*/
setCatchHandler(handler: RouteHandler) {
this.catchHandler = normalizeHandler(handler)
}
}

export {Route};
49 changes: 38 additions & 11 deletions packages/workbox-routing/src/Router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,18 +244,45 @@ class Router {
responsePromise = Promise.reject(err);
}

if (responsePromise instanceof Promise && this._catchHandler) {
responsePromise = responsePromise.catch((err) => {
if (process.env.NODE_ENV !== 'production') {
// Still include URL here as it will be async from the console group
// and may not make sense without the URL
logger.groupCollapsed(`Error thrown when responding to: ` +
` ${getFriendlyURL(url)}. Falling back to Catch Handler.`);
logger.error(`Error thrown by:`, route);
logger.error(err);
logger.groupEnd();
// Get route's catch handler, if it exists
const catchHandler = route && route.catchHandler;

if (responsePromise instanceof Promise && (this._catchHandler || catchHandler)) {
responsePromise = responsePromise.catch(async (err) => {
// If there's a route catch handler, process that first
if (catchHandler) {
if (process.env.NODE_ENV !== 'production') {
// Still include URL here as it will be async from the console group
// and may not make sense without the URL
logger.groupCollapsed(`Error thrown when responding to: ` +
` ${getFriendlyURL(url)}. Falling back to route's Catch Handler.`);
logger.error(`Error thrown by:`, route);
logger.error(err);
logger.groupEnd();
}

try {
return await catchHandler.handle({url, request, event, params});
} catch (catchErr) {
err = catchErr;
}
}
return this._catchHandler!.handle({url, request, event});

if (this._catchHandler) {
if (process.env.NODE_ENV !== 'production') {
// Still include URL here as it will be async from the console group
// and may not make sense without the URL
logger.groupCollapsed(`Error thrown when responding to: ` +
` ${getFriendlyURL(url)}. Falling back to global Catch Handler.`);
logger.error(`Error thrown by:`, route);
logger.error(err);
logger.groupEnd();
}

return this._catchHandler.handle({url, request, event});
}

throw err;
});
}

Expand Down
37 changes: 37 additions & 0 deletions test/workbox-routing/sw/test-Router.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,43 @@ describe(`Router`, function() {
expect(responseBody).to.eql(EXPECTED_RESPONSE_BODY);
});

it(`should fall back to the Route's catch handler if there's an error in the Route's handler, if set`, async function() {
const router = new Router();
const route = new Route(
() => true,
() => Promise.reject(new Error()),
);
route.setCatchHandler(() => new Response(EXPECTED_RESPONSE_BODY));
router.registerRoute(route);

// route.match() always returns true, so the Request details don't matter.
const request = new Request(location);
const event = new FetchEvent('fetch', {request});
const response = await router.handleRequest({request, event});
const responseBody = await response.text();

expect(responseBody).to.eql(EXPECTED_RESPONSE_BODY);
});

it(`should fall back to the global catch handler if there's an error in the Route's catch handler`, async function() {
const router = new Router();
const route = new Route(
() => true,
() => Promise.reject(new Error()),
);
route.setCatchHandler(() => Promise.reject(new Error()));
router.setCatchHandler(() => new Response(EXPECTED_RESPONSE_BODY));
router.registerRoute(route);

// route.match() always returns true, so the Request details don't matter.
const request = new Request(location);
const event = new FetchEvent('fetch', {request});
const response = await router.handleRequest({request, event});
const responseBody = await response.text();

expect(responseBody).to.eql(EXPECTED_RESPONSE_BODY);
});

it(`should return a response from the first matching route when there are multiple potential matches`, async function() {
const router = new Router();
const response1 = 'response1';
Expand Down

0 comments on commit 1e8166e

Please sign in to comment.