通过 Service Workerfetch 事件可监听页面中的所有请求,因此可通过缓存构建响应,以减少请求的响应时间。Workbox 中的 workbox-routing 模块为我们提供了便捷的方式来匹配并处理请求,本章我们将对该模块进行详细介绍。

# 基本使用

我们通过调用 workbox.routing.registerRoute 方法来注册并处理请求,方法参数依次为:

  • capture:请求匹配规则,类型为字符串、正则表达式、函数或 workbox.routing.Route
  • handler:请求处理函数,返回值为 Promise<Response>,参数为含有以下属性的对象:
    • url:匹配到的请求地址,类型为:URL。
    • event:触发请求的 FetchEvent 对象,该属性为可选属性。
    • request:触发请求的 Request 对象,该属性为可选属性。
    • params:请求匹配函数的返回值,类型为非空数组、非空对象或 undefined
    • 注:该参数的值亦可为含有属性 handle的对象,且属性 handle 的值与参数值为函数时一致。
  • method:请求方法,值为 GETHEADPOSTPATCHPUTDELETE,默认值为 GET

下面我们通过一些示例来介绍 workbox.routing.registerRoute 的使用:

  1. capture 的值为正则表达式时:
workbox.routing.registerRoute(
  new RegExp('/styles/.*\\.css'),
  ({ url, event, request, params }) => Promise.resolve(new Response(...))
);

示例中,我们注册了 /styles 路径下 css 文件请求的监听处理,需要注意的是,由于同源策略的影响,此时 capture 的值将无法匹配第三方站点的请求,比如:

https://cdn.third-party-site.com/styles/main.css

如果想要正确匹配第三方资源,只需要保证正则表达式能够与请求 URL 的开头相匹配即可,因此 capture 的值可修改为:new RegExp('https://.*/styles/.*\\.css')

  1. capture 的值为函数时:
workbox.routing.registerRoute(
  ({ url, event, request }) => true,
  ({ url, event, request, params }) => Promise.resolve(new Response(...))
);

如果 capture 的返回值为 truthy,则立刻调用 handler 处理函数,如果返回值为非空数组或非空对象,返回值将以 params 的形式传递给 handler 处理函数。函数的参数为含有以下属性的对象:

  • url:匹配到的请求地址,类型为:URL
  • event:触发请求的 FetchEvent对象,该属性为可选属性。
  • request:触发请求的 Request 对象,该属性为可选属性
  1. capture 的值为 workbox.routing.Route 时:
workbox.routing.registerRoute(
  new workbox.routing.Route(
    ({ url, event, request }) => true,
    ({ url, event, request, params }) => Promise.resolve(new Response(...))
  );
);
  1. capture 的值为 workbox.routing.Route 时,参数 handlermethod 将被忽略,故无需设置其值。

workbox.routing.Route 的参数依次为 matchhandlermethod

  • match 等同于 workbox.routing.registerRoute 方法中的 capture 参数(值为函数时)。
  • handlermethod 等同于 workbox.routing.registerRoute方法中的 handlermethod 参数。

# 全局处理函数

上文我们对 workbox.routing.registerRoute 方法的使用进行了介绍,然而有时我们可能需要处理未被成功匹配的请求,此时便可通过以下方式来设置默认处理函数:

workbox.routing.setDefaultHandler(({ url, event, request, params }) => {
  return Promise.resolve(new Response(...));
});

在另外的一些场景下,如果注册的路由抛出异常,可能需要捕获异常并做一些降级处理(比如网络异常后通过缓存构建响应),可通过以下方式进行处理:

workbox.routing.setCatchHandler(({ url, event, request, params }) => {
  return Promise.resolve(new Response(...));
});

方法 workbox.routing.setDefaultHandlerworkbox.routing.setCatchHandler 的参数与上文中所讨论的 handler 处理函数的使用一致,此处不再重述。

# 高级使用

同预缓存一样,我们不仅可以通过 workbox.routing.registerRoute 来快速注册请求响应,也可通过 workbox.routing.DefaultRouter 来自行接管 Service Worker的 fetch 事件,比如:

const router = new workbox.routing.DefaultRouter();

router.registerRoute(new Route(matchCb, handlerCb));
router.registerRoute(new RegExpRoute(new RegExp(...), handlerCb));

self.addEventListener('fetch', event => {
  const responsePromise = router.handleRequest(event);
  if (responsePromise) {
    event.respondWith(responsePromise);
  } else {
    // 不匹配后的逻辑处理
  }
});

注:workbox.routing.DefaultRouter 的实例方法 registerRoute 参数必须为 workbox.routing.Route 类型。

# 注意事项

由于在 Workbox 中,会按照路由注册的先后顺序对请求进行匹配(亦包含通过 workbox.precaching.precacheAndRoute 注册的预缓存路由),一旦有路由匹配到该请求,便利用该路由进行处理。对该规则处理不当,很可能会造成意想不到的结果,比如:

workbox.routing.registerRoute(
  new RegExp('/styles/.*\\.css'),
  handlerCb
);

workbox.routing.registerRoute(
  '/styles/example.ac29.css',
  handlerCb1
);

上例中,我们的本意是想使用 handlerCb1 来响应 /styles/example.ac29.css 请求,但由于该请求亦能被 handlerCb 处理,且此路由优先于 handlerCb1 注册,所以实际上请求由 handlerCb 进行响应。为避免此类错误的出现,我们应该按照匹配范围从小到大的顺序进行路由规则定义。

# 总结

本章我们对 workbox-routing 模块的使用及使用过程中可能存在的问题进行了详细介绍,通过该模块,我们具备了拦截并处理页面中所有请求的能力,但仍需利用实战篇中讨论的请求策略、缓存置换等机制来更高效地完成请求的响应,因此下一章,我们将对 Workbox 中的请求策略、缓存置换进行讨论。

阅读全文