PWA 学习笔记之 fetch API

in #cn-programming7 years ago (edited)

Progressive Web App (PWA)是渐进增强 Web App,它能让我们在不可靠的网络上也能快速加载、能够接收桌面通知、具有桌面图标,并且可采用顶层全屏体验的方式加载。

fetch-api.png

(背景素材来源:Google - 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 项目的团队的前五位成员,实现代码如下:

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 对象,实现代码如下:

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):

fetch-api.png

(图片来源:caniuse - fetch)

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

  • 当接收到一个代表错误的 HTTP 状态码时,从 fetch() 返回的 Promise 不会被标记为 reject, 即使该 HTTP 响应的状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的 ok 属性设置为 false ), 仅当网络故障时或请求被阻止时,才会标记为 reject
  • 默认情况下, fetch 不会从服务端发送或接收任何 cookies, 如果站点依赖于用户 session,则会导致未经认证的请求(要发送 cookies,必须设置 credentials 选项)。

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

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 标准 中该方法的声明如下:

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

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

具体示例如下:

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 处理等。这里就不再展开了,有兴趣的小伙伴,请自行查阅相关资料。