Fetch API 入门:替代繁琐的 AJAX
随着 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 的核心方法,同时定义在 window
和 WorkerGlobalScope
,
因此可以作为通用的方法来使用。在 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/
携带 Cookie
fetch
第二个参数的 credentials
属性的默认值在 Chrome 中实现不一致。
根据 Fetch API 标准,该参数的默认值为 same-origin
,
即同域时携带 Cookie(这一点 fetch
与 XMLHttpRequest
的表现一致)。
但 Chrome 目前的实现中(Harttle 的版本是 56.0.2924.87 64-bit),
window.fetch()
默认不带 Cookie,即omit
;
ServiceWorkerGlobalScope.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 相关标准、文档、以及本站的其他文章。
- MDN Fetch Method: https://developer.mozilla.org/zh-CN/docs/Web/API/GlobalFetch/fetch
- Fetch Spec: https://fetch.spec.whatwg.org/#fetch-method
- 立即使用 Service Worker!
- Service Worker 调试技巧
本文采用 知识共享署名 4.0 国际许可协议(CC-BY 4.0)进行许可,转载注明来源即可: https://harttle.land/2017/04/22/fetch-api.html。如有疏漏、谬误、侵权请通过评论或 邮件 指出。