前端 fetch 通信

作者:villainthr

随着前端异步的发展, XHR 这种耦合方式的书写不利于前端异步的 Promise 回调。而且,写起来也是很复杂.。fetch API 本来是在 SW(ServiceWorkers) 中提出的,不过,后面觉得好用, 就把他挂载到 window 对象下。 这样,在前端的正常通信中, 我们也可以直接调用。 但fetch 毕竟比较新,看一下他的兼容性。

在 PC 端上, 就 FF, Opera 和 Chrome 比较 fashion. mobile 的话, 基本上是不能用的. 当然, 前端一直是个拥抱变化的职业, 官方已经有一个现成的 polyfill 可以使用. 这样的话, 就没必要过多担心.

每用到一个新的 feature, 我们首先得知道他能不能用. Modernizr 这个库做的挺好的. 这里, 我们简单的了解一下就 ok 了。

let isFetch = window.fetch?true:false;

fetch 基本格式

可以说, fetch 就是 ajax + Promise. 使用的方式和 jquery 提供的 $.ajax() 差不多。

fetch('./api/some.json')  
  .then(  
    function(response) {  
      if (response.status !== 200) {  
        console.log(`返回的响应码${response.status}`);  
        return;  
      }

      // 获得后台实际返回的内容
      response.json().then(function(data) {  
        console.log(data);  
      });  
    }  
  )  
  .catch(function(err) {  
    console.log('Fetch Error :-S', err);  
  });

上面的 demo 很好的参数了, fetch 的几个特点.

  • then()
  • catch()
  • json()

then 和 catch 是 promise 自带的两个方法, 我这里就不多说了. 我们来看一下,json 是干嘛的。

因为返回回来的数据不仅仅包括后台的返回的 Text 内容, 还有一些 Headers. 所以在, then 方法里面返回来的 res 实际上并不是我们在业务中想要的内容. 就和在 XHR 里返回数据是一个道理, 我们最终要的是 responseText 这个数据. 而 json() 方法实际做的事情,就是调用 JSON.parse() 处理数据, 并且返回一个新的 Promise. 看一下 polyfill 源码就应该了解。

this.json = function() {
    return this.text().then(JSON.parse)
}

这里需要注意的是,fetch 中的 response/request 都是 stream 对象。

fetch 传输格式

上面的 demo 是一个 get 方法的请求, 当然, 除了 get , 还有其他的 HTTP Method, PUT, DELETE, POST, PATCH 等. 这里, 我们就说一个 POST, 其他方法的基本格式还是类似的。

fetch("http://www.example.org/submit.php", {
  method: "POST",
  headers: {
    "Content-Type": "application/x-www-form-urlencoded"
  },
  body: "this is a post Msg"
}).then(function(res) {
  if (res.ok) {
    // doSth
  } else if (res.status == 401) {
    // doSth
  }
});

看起来 fetch 和 $.ajax 并没有多大的区别,但是, fetch 里面的内容,真不少。往底层看一看, fetch 实际上是 Request , Headers , Response 3个接口的整合. 不过, 这3个只能在 SW 里面使用。 这里当做原理,参数一下即可。

Headers 操作

Headers 的操作无非就是 CRUD, 这里我就不过多赘述,直接看代码吧:

var content = "Hello World";
var reqHeaders = new Headers();
reqHeaders.append("Content-Type", "text/plain"
reqHeaders.append("Content-Length", content.length.toString());
reqHeaders.append("X-Custom-Header", "自定义头");

当然, 你也可以使用字面量的形式:

reqHeaders = new Headers({
  "Content-Type": "text/plain",
  "Content-Length": content.length.toString(),
  "X-Custom-Header": "自定义头",
});

接下来就是, 头部的内容的检测相关。

console.log(reqHeaders.has("Content-Type")); // true
console.log(reqHeaders.has("Set-Cookie")); // false
reqHeaders.set("Content-Type", "text/html");
reqHeaders.append("X-Custom-Header", "新增自定义头");

console.log(reqHeaders.get("Content-Length")); // 11
console.log(reqHeaders.getAll("X-Custom-Header")); // ["自定义头", "新增自定义头"]

reqHeaders.delete("X-Custom-Header");
console.log(reqHeaders.getAll("X-Custom-Header")); // []

不过, 鉴于安全性的考虑, 有时候在多人协作或者子版块管理时, 对于头部的限制还是需要的. 这里, 我们可以通过 guard 属性, 设置 Headers 的相关策略.。

guard 通常可以取 "immutable", "request", "request-no-cors", "response", "none"。

我们这里不探讨全部, 仅仅看一下 request 这个选项。

当你设置了 request 之后, 如果你设置的 Header 涉及到 forbidden header name (这个是浏览器自动设置的), 那么该次操作是不会成功的。

forbidden header name 通常有:

  • Accept-Charset
  • Accept-Encoding
  • Access-Control-Request-Headers
  • Access-Control-Request-Method
  • Connection
  • Content-Length
  • Cookie
  • Cookie2
  • Date
  • DNT
  • Expect
  • Host
  • Keep-Alive
  • Origin
  • Referer
  • TE
  • Trailer
  • Transfer-Encoding
  • Upgrade
  • Via

对比与 fetch, 我们没有办法去设置 Headers的 guard, 所以,这只能在 SW 里使用。

Request 操作

Request 的基本用法和 fetch 差不多。

var uploadReq = new Request("/uploadImage", {
  method: "POST",
  headers: {
    "Content-Type": "image/png",
  },
  body: "image data"
});

fetch("/uploadImage", {
  method: "POST",
  headers: {
    "Content-Type": "image/png",
  },
  body: "image data"
});

那 fetch 有什么用呢?

关键的地方在于,fetch 实际上就是 request/reponse 的容器,request/response 相当于就是两个元数据,fetch 只是实际进行的操作。所以,为了达到更高的复用性,我们可以 ajax 的请求,实例化为一个个具体的对象。

var getName = new Request(...,{//...});
var getGender = new Request(...,{//...});

// 发送请求
fetch(getName)
.then((res)=>{});

fetch(getGender)
.then((res)=>{});

在浏览器里, 一切请求都逃不过跨域和不跨域的问题. fetch 也是. 对于跨域的请求, 主要的影响还是体现在 Response 中, 这 fetch Request 这, 没多大影响. 不过, 我们需要在 fetch 设置 mode 属性, 来表示这是一个跨域的请求。

fetch('https://www.villainhr.com/cors-enabled/some.json', {mode: 'cors'})  
  .then(function(response) {  
    return response.text();  
  })

常用的 mode 属性值有:

  • same-origin: 表示只请求同域. 如果你在该 mode 下进行的是跨域的请求的话, 那么就会报错.
  • no-cors: 正常的网络请求, 主要应对于没有后台没有设置 Access-Control-Allow-Origin. 话句话说, 就是用来处理 script, image 等的请求的. 他是 mode 的默认值.
  • cors: 用来发送跨域的请求. 在发送请求时, 需要带上.
  • cors-with-forced-preflight: 这是专门针对 xhr2 支持出来的 preflight,会事先多发一次请求给 server,检查该次请求的合法性。

另外, 还有一个关于 cookie 的跨域内容. 在 XHR2 中,我们了解到, withCredentials 这个属性就是用来设置在进行跨域操作时, 对不同域的 Server 是否发送本域的 cookie. 一般设置为 omit(不发送). 在 fetch 当中, 使用的是 credentials 属性。

credentials 常用取值为:

  • omit: 发送请求时,不带上 cookie. 默认值.
  • same-origin: 发送同域请求时,会带上 cookie.
  • include: 只要发送请求,都会带上 cookie.

所以, 如果你想发送 ajax 时, 带上 cookie, 那么你就需要使用 same-origin, 如果想在跨域时也带上 cookie, 那么就需要 include。

// 跨域请求
fetch('https://www.villainhr.com/cors-enabled/some.json', {mode: 'cors',credentials:'include'})  
  .then(function(response) {  
    return response.text();  
  })

Response 操作

response 应该算和 fetch 最为接近的一个对象. Response 的实际其实就是 fetch 回调函数传回的参数. Response 中比较常用的属性有四个: status, statusText, ok, type。

  • status: 返回的状态码. 100~500+
  • statusText: 返回状态码代表的含义. 比如, 返回"ok".
  • ok: 用来检差 status 是否在200和299之间.
  • type: 表示请求是否跨域, 或是否出错. 取值为: “basic”, “cors”, “default”, “error” 或“opaque”.
fetch('https://www.villainhr.com/cors-enabled/some.json', {mode: 'cors',credentials:'include'})  
  .then(function(response) {  
    // ...
  })

这里, 我们主要关心一下 type 上面挂载的一些属性.

  • basic: 同域通信类别. 可以正常的访问 response 的 header(除了 Set-Cookie 头)。
  • cors: 跨域通信类别. 一般只能访问以下的头: - Cache-Control - Content-Language - Content-Type - Expires - Last-Modified - Pragma
  • error: 网络错误类别。
  • opaque: 无法理解类别. 当使用 no-cors 发送跨域请求时,会触发。

另外,在 response 上面,还挂载了几个常用的方法: text(),json().

  • text(): 主要用来处理 server 返回的 string 类型数据。
  • josn(): 主要用来处理 server 返回的 json 类型数据。

使用方式都是流式 API.

fetch('https://www.villainhr.com/cors-enabled/some.json')  
  .then(function(res) {  
    res.text().then((text)=>{...})
    res.json().then((obj)=>{...})
  })

body 处理

我们通过 ajax 请求数据时,可能会收到,ArrayBuffer,Blob/File,string,FormData 等等。并且,在发送的时候比如:

var form = new FormData(document.getElementById('login-form'));
fetch("/login", {
  method: "POST",
  body: form
})

fetch 会自动设置相关的 Content-Type 的头。另外,如果我们可以手动生成一个响应流(方便后面其他操作)。

var res = new Response(new File(["chunk", "chunk"], "archive.zip",{ type: "application/zip" }));

流的处理

因为,req/res 都是以流的形式存在的,即,req/res 的 body 只能被使用一次。相当于就是一个文件从缓存读到硬盘里面,那么原来文件就已经消失了。我们可以通过 bodyUsed 去检查,该对象是否已经被使用。

var res = new Response("one time use");
console.log(res.bodyUsed); // false
res.text().then(function(v) {
  console.log(res.bodyUsed); // true
});
console.log(res.bodyUsed); // true

res.text().catch(function(e) {
  console.log("Tried to read already consumed Response");
});

这样做的原因主要是为了让以后 Web 更好的处理视频的相关数据。那如果我有时候想要使用多次,那该怎么办? 例如,我们 Service Worker 中,使用 caches API 缓存响应,然后后面我还要将该响应返回给浏览器,那么这里 response 流就被使用了两次。这里,就和普通的流操作一样,将该流克隆一份,使用:

addEventListener('fetch', function(evt) {
  var sheep = new Response("Dolly");
  console.log(sheep.bodyUsed); // false
  var clone = sheep.clone();
  console.log(clone.bodyUsed); // false

  clone.text();
  console.log(sheep.bodyUsed); // false
  console.log(clone.bodyUsed); // true

  evt.respondWith(cache.add(sheep.clone()).then(function(e) {
    return sheep;
  });
});

参考

基本的内容就是上述内容. 如果想更详细参考的话, 请查阅:

This API is so Fetching!

introduction-to-fetch

原文链接:http://ivweb.io/topic/5855f6a873eaa3986e3f8e2f

原文链接:

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏冷冷

Zuul:构建高可用网关之多维度限流

对请求的目标URL进行限流(例如:某个URL每分钟只允许调用多少次) 对客户端的访问IP进行限流(例如:某个IP每分钟只允许请求多少次) 对某些特定用户或者用户...

4988
来自专栏Ken的杂谈

Spring Cloud 入门教程9、服务限流/API限流(Zuul+RateLimiter)

RateLimiter是Google开源的实现了令牌桶算法的限流工具(速率限制器)。http://ifeve.com/guava-ratelimiter/

2773
来自专栏张戈的专栏

LVS+Keepalived负载均衡主备&双主架构全攻略

LVS+Keepalived 介绍 LVS LVS 是 Linux Virtual Server 的简写,意即 Linux 虚拟服务器,是一个虚拟的服务器集...

3634
来自专栏IT技术精选文摘

WebSocket协议深入探究

一、内容概览 WebSocket的出现,使得浏览器具备了实时双向通信的能力。本文由浅入深,介绍了WebSocket如何建立连接、交换数据的细节,以及数据帧的格式...

41412
来自专栏IT 指南者专栏

【SSH框架】之Spring系列(一)

微信公众号:compassblog 欢迎关注、转发,互相学习,共同进步! 有任何问题,请后台留言联系! 1、前言 前面更新过几篇关于 Struts2 框架和 H...

26413
来自专栏黄Java的地盘

WebSocket系列之如何建立和维护可靠的连接

通过前四篇博客,相信读者对于WebSocket的使用和数据(不论是ArrayBuffer还是String)传输都有了一个深刻的了解。现在我们来介绍下,我在使用W...

1341
来自专栏恰同学骚年

ASP.Net请求处理机制初步探索之旅 - Part 1 前奏

开篇:ASP.Net是一项动态网页开发技术,在历史发展的长河中WebForm曾一时成为了ASP.Net的代名词,而ASP.Net MVC的出现让这项技术更加唤发...

712
来自专栏极客慕白的成长之路

HTTP协议原理及实践

HTTP是在应用层,基于传输层 0x1 低三层 物理层主要作用是定义物理设备如何传输数据 数据链路层在通信的实体间建立数据链路连接 网络层为数据在结...

1133
来自专栏向治洪

React Native网络请求fetch简单封装

在原生应用开发中,为了方便业务开发人员使用,我们一般会对网络库进行一些上传封装,而不是直接使用,例如基于AFNetworking库的ios请求上层封装,Andr...

3997
来自专栏企鹅号快讯

WebSocket:5分钟从入门到精通

作者:程序猿小卡 https://segmentfault.com/a/1190000012709475 一、内容概览 WebSocket的出现,使得浏览器具备...

2028

扫码关注云+社区