PWA 学习笔记之 fetch API

View this thread on: d.buzz | hive.blog | peakd.com | ecency.com
·@semlinker·
0.000 HBD
PWA 学习笔记之 fetch API
Progressive Web App (PWA)是渐进增强 Web App,它能让我们在不可靠的网络上也能快速加载、能够接收桌面通知、具有桌面图标,并且可采用顶层全屏体验的方式加载。

![fetch-api.png](https://steemitimages.com/DQmb6srEawUexZ7jJQrdhDW8mzexSAB4VefxDfvvtxRSqKS/fetch-api.png)

(背景素材来源:[Google - progressive-web-apps](https://developers.google.com/web/progressive-web-apps/))

Progressive Web App 具有的以下主要特点:

- **渐进式** - 适用于选用任何浏览器的所有用户,因为它是以渐进式增强作为核心宗旨来开发的。
- **自适应** - 适合任何机型:桌面设备、移动设备、平板电脑或任何未来设备。
- **持续更新** - 在服务工作线程更新进程的作用下时刻保持最新状态。
- **安全** - 通过 HTTPS 提供,以防止窥探和确保内容不被篡改。
- **可安装** - 用户可免去使用应用商店的麻烦,直接将对其最有用的应用“保留”在主屏幕上。
- **可链接** - 可通过网址轻松分享,无需复杂的安装。

PWA 基于很多新的 API 和新的技术,如 fetch API、CacheStorage API、Background Sync、Service Worker 和 IndexedDB 等。然而要想真正了解并掌握 PWA,就必须了解它背后基于的技术。因此后续的文章,我们将逐一介绍 PWA 的相关技术。有兴趣的小伙伴们赶紧上车,我们将从 fetch API 开始,开启 PWA 的学习旅程。

fetch 中文的意思为获取,即通过它我们可以用来获取资源。在前端的日常工作中,我们通常需要从 API 获取数据,然后对数据进行处理或展示。在与服务器交互过程中使用的数据格式一般是 JSON,接下来我们先来体验一下,利用 fetch API 获取 [angular](https://github.com/angular/angular) 项目的团队的前五位成员,实现代码如下:

```javascript
if("fetch" in this) {
  fetch("https://api.github.com/orgs/angular/members?page=1&per_page=5")
    .then(res => res.json())
    .then(console.dir)
}
```

要实现同样的功能,我们当然也可以使用 XMLHttpRequest 对象,实现代码如下:

```javascript
var xhr = new XMLHttpRequest();
xhr.open('GET', "https://api.github.com/orgs/angular/members?page=1&per_page=5");
xhr.responseType = 'json';
xhr.onload = function() {
  console.dir(xhr.response);
};
xhr.send();
```

是不是感觉使用 fetch 简单很多,然而我们并不能随心所欲的使用它,因为它有兼容性问题,具体如下下图所示 (详细信息可浏览 [can i use - fetch](https://caniuse.com/#search=fetch)):

![fetch-api.png](https://steemitimages.com/DQmW498ngn2oScq7E3TSL8TtE1Wsusrh784gfs1u99izZ1F/fetch-api.png)

(图片来源:[caniuse - fetch](https://caniuse.com/#search=fetch))

对于大多数小伙伴来说,应该更熟悉  jQuery.ajax()。fetch 规范与 jQuery.ajax() 的主要区别如下:

- 当接收到一个代表错误的 HTTP 状态码时,从 `fetch()` 返回的 Promise **不会被标记为 reject,** 即使该 HTTP 响应的状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的 ok 属性设置为 false ),  **仅当网络故障时或请求被阻止时,才会标记为 reject**。
- 默认情况下, `fetch` **不会从服务端发送或接收任何 cookies**, 如果站点依赖于用户 session,则会导致未经认证的请求(要发送 cookies,必须设置 [credentials](https://developer.mozilla.org/zh-CN/docs/Web/API/GlobalFetch/fetch#%E5%8F%82%E6%95%B0) 选项)。

除了使用 fetch API 来获取 JSON 数据,我们也可以使用它来获取其它资源,比如普通文本、图片资源等。下面我们来看一下如何使用 fetch API 来获取图片资源,并在页面中显示。

```javascript
if ("fetch" in this) {
  let myImage = document.querySelector('img');
  fetch('https://mdn.github.io/dom-examples/streams/grayscale-png/tortoise.png')
    .then(function(response) {
       return response.blob();
    })
    .then(function(myBlob) {
       let objectURL = URL.createObjectURL(myBlob);
       myImage.src = objectURL;
    });
}           
```

前面的两个示例中,我们通过 fetch 方法分别实现 JSON 数据的读取和图片的资源的获取功能。fetch 方法是 Fetch API 的核心方法,同时定义在 window 和 WorkerGlobalScope 环境中,因此我们可以在 Service Worker 环境中使用它。

在 [Fetch 标准](https://fetch.spec.whatwg.org/#fetch-method) 中该方法的声明如下:

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

通过观察上面的方法签名,我们可以知道 fetch 方法支持两个参数,调用后返回一个 Promise 对象。第二个参数是一个参数对象,用来初始化 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`。

了解详细的信息,请阅读 [https://developer.mozilla.org/zh-CN/docs/Web/API/GlobalFetch/fetch](https://developer.mozilla.org/zh-CN/docs/Web/API/GlobalFetch/fetch)。

在 PWA 应用中,fetch API 的用武之地在于资源(比如图片、脚本文件或样式文件等)的获取。为了能够保证用户的离线体验,我们可以在获取资源时,对资源进行缓存。

具体示例如下:

```javascript
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      // 若缓存不存在,则使用fetch API从网上获取,然后利用Cache API缓存资源。
      return response || fetch(event.request).then((response) => {
          return caches.open('v1').then((cache) => {
            cache.put(event.request, response.clone());
            return response;
          });
     }); 
   })
  );
});
```

对于 Cache API,我们下一篇会介绍。这里需要注意的是,在使用 cache.put() 保存资源时,我们调用 response 对象的 clone() 方法,而不是直接保存 response 对象。为什么需要克隆响应对象呢?**这是因为 Request 和 Response 的 body (响应体)只能被读取一次!它们有一个属性叫 bodyUsed,读取一次之后设置为 true,就不能再读取了。**

本文只是简单介绍了 fetch API,其实 fetch API  还有很多东西需要进一步了解,如设置请求头、处理 Text/HTML 资源、表单提交、Cookies 与 CORS 处理等。这里就不再展开了,有兴趣的小伙伴,请自行查阅相关资料。
👍 , ,