随着 PWA 进入人们的视野,Fetch 作为除了 AJAX 之外的第二个 JavaScript HTTP API 开始引起人们的关注。 Service Worker 中通常利用该 API 进行真正的网络请求并应用相应的缓存策略。 本文简要介绍 Fetch API 的使用,Service Worker 与 Window 中的实现差异,以及跨域 Fetch 的问题。

目前 Web 异步应用都是基于 XMLHttpRequest/ActiveXObject 实现的, 这些对象不是专门为资源获取而设计的,因而它们的 API 非常复杂,同时还需要开发者处理兼容性问题。 虽然开发者普遍使用 $.ajax() 这样的上层包装,但 Fetch API 意在提供更加方便和一致的原生 API, 同时统一 Web 平台上的资源获取行为,包括外链脚本、样式、图片、AJAX 等等。

使用示例

Fetch 方法 是 Fetch API 的核心方法,同时定义在 windowWorkerGlobalScope, 因此可以作为通用的方法来使用。在 Fetch 标准 中该方法的声明如下。

partial interface WindowOrWorkerGlobalScope {
  [NewObject] Promise<Response> fetch(RequestInfo input, optional RequestInit init);
};

fetch 方法返回一个 Promise,因此使用起来非常有现代感:

fetch('https://harttle.land')
.then(function(res) {
    // 使用该 HTTP 响应
}).catch(function(err) {
    // 处理错误 err
});

Fetch 参数

fetch 方法的第二个参数是一个参数对象,用来初始化 Request。下面列出了几个重要的属性:

  • method:请求方法,可取 "GET", "POST" 等,默认为 "GET"
  • mode:请求模式,可取 "no-cors", "cors", "same-origin"
  • credentials:是否携带 Cookie,可取:"omit", "same-origin", "include"
  • cache: 缓存模式,可取: default, no-store, reload, no-cache, force-cache, only-if-cached

简单的使用方法如下:

fetch('https://harttle.land', {
    method: 'GET',
    mode: 'no-cors',
    cache: 'default'
})

mode 是 Service Worker 实现中最重要的参数,它的值可能是:

  • cors: 采用跨域资源共享的方式去获取。要求对方服务器启用了 CORS 响应头,可以读取响应内容。否则会跨域失败。
  • no-cors:采用非跨域资源共享的方式去获取。不要求对方的 CORS 设置,不可读取响应内容。
  • same-origin:采用同域的方式去获取,如果 URL 跨域就会失败。

更多参数解释参考:https://developer.mozilla.org/zh-CN/docs/Web/API/GlobalFetch/fetch

跨域 Fetch

Service Worker 实现离线缓存时,通常需要监听 fetch 事件并应用缓存。 fetch 事件作用于当前受控制的页面,既可以拦截同域的请求也可以拦截跨域的请求。 这意味着 harttle.org 的页面请求 harttle.land 中的资源时, harttle.org 的 Service Worker 会收到 fetch 事件,但 harttle.land 的 Service Worker 不会 (在启用了 Foreign Fetch 的 Service Worker 中,情况可能略有不同)。

在跨域资源请求的情况下,Service Worker 中进行 Fetch 和写 CacheStorage 操作都可以成功。 但需要注意的是 Fetch API 的设计比 AJAX 更为底层,但二者的 CORS 策略是一致的。 具体地,如果 Fetch 时声明了 no-cors 就可以跨域 Fetch,其代价是无法读取响应状态码和响应体内容。

fetch('https://harttle.land/foo.jpg', {mode: 'no-cors'}).then(res => {
    if (!res.ok) return;
    caches.open('harttle').then(cache => {
        cache.put('https://harttle.land/foo.jpg', res)
    })
})

虽然跨域情况下无法知晓响应体和状态码,但考虑到 Service Worker 需要了解 Fetch 是否成功来进行缓存, Response 给出了 .ok 属性来判断获取成功与否。

参见 Harttle 的 Demo:https://demo.service-worker.org/cors-fetch/

fetch 第二个参数的 credentials 属性的默认值在 Chrome 中实现不一致。 根据 Fetch API 标准,该参数的默认值为 same-origin, 即同域时携带 Cookie(这一点 fetchXMLHttpRequest 的表现一致)。

但 Chrome 目前的实现中(Harttle 的版本是 56.0.2924.87 64-bit), window.fetch() 默认不带 Cookie,即omitServiceWorkerGlobalScope.fetch() 则默认携带 Cookie,即same-origin

为了避免不一致的 Bug,建议总是给 fetch 设置 credentials 参数:

fetch('https://harttle.land', {credentials: 'same-origin'}).then(res => {
    // 处理 Response
})

参见 Harttle 的 Demo:https://demo.service-worker.org/fetch-with-credentials/

默认 Mode

fetch 第二个参数的 mode 属性在 Chrome 下默认值不正确。 根据 WHATWG 的 Fetch API 标准,Request 的模式(mode)默认值为 "no-cors"

2.2.5. Requests

A request has an associated mode, which is "same-origin", "cors", "no-cors", "navigate", or "websocket". Unless stated otherwise, it is "no-cors".

Even though the default request mode is "no-cors", standards are highly discouraged from using it for new features. It is rather unsafe. "navigate" and "websocket" are special values for the HTML Standard. [HTML]

Chrome 下默认值为 "cors",这会导致用默认参数 fetch 未开启 CORS 的跨域资源时失败。 因此在编写 Service Worker 时,如果无需读取 Response 内容,要显式地声明 mode: 'no-cors'

参见 Harttle 的 Demo:https://demo.service-worker.org/cors-fetch/

扩展阅读

下面是 Fetch API 相关标准、文档、以及本站的其他文章。

本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可,转载注明来源即可: https://harttle.land/2017/04/22/fetch-api.html。如有疏漏、谬误、侵权请通过评论或 邮件 指出。