首页 > 技术文章 > Fetch API

swan1014 2017-09-18 17:39 原文

一,基本用法

Ajax操作所用的XMLHttpRequest对象,已经有十多年的历史,它的API设计并不是很好,输入、输出、状态都在同一个接口管理,容易写出非常混乱的代码。Fetch API是一种新规范,用来取代XMLHttpRequest对象。它主要有两个特点,一是接口合理化,Ajax是将所有不同性质的接口都放在XHR对象上,而Fetch是将它们分散在几个不同的对象上,设计更合理;二是Fetch操作返回Promise对象,避免了嵌套的回调函数。

下面的代码检查浏览器是否部署了Fetch API。

if ("fetch" in window){
  // 支持
} else {
  // 不支持
}

下面是一个Fetch API的简单例子。

fetch(url).then(function (response) {
  return response.json();
}).then(function (jsonData) {
  console.log(jsonData);
}).catch(function () {
  console.log('出错了');
});

上面代码向指定的URL发出请求,得到回应后,将其转为JSON格式,输出到控制台。如果出错,则输出一条提示信息。注意,fetch方法返回的是一个Promise对象。

二,stream数据流

Fetch API最大的特点是,除了返回Promise对象,还有一点就是数据传送是以数据流(stream)的形式进行的。对于大文件,数据是一段一段得到的。

response.text().then(function (responseText) {
  console.log(responseText);
}

上面代码中的text(),其实就是一个数据流读取器,并使用指定格式解读。

Fetch API提供以下五个数据流读取器。

    • .text():返回字符串
    • .json():返回一个JSON对象
    • .formData():返回一个FormData对象
    • .blob():返回一个blob对象
    • .arrayBuffer():返回一个二进制数组

数据流只能读取一次,一旦读取,数据流就空了。再次读取就不会得到结果。解决方法是在读取之前,先使用.clone()方法,复制一份一模一样的副本。

处理数据流的getStream函数代码如下。

var progress = 0;
var contentLength = 0;
var getStream = function (reader) {
  return reader.read().then(function (result) {
    // 如果数据已经读取完毕,直接返回
    if (result.done) {
      return;
    }
    // 取出本段数据(二进制格式)
    var chunk = result.value;
    var text = '';
    // 假定数据是UTF-8编码,前三字节是数据头,
    // 而且每个字符占据一个字节(即都为英文字符)
    for (var i = 3; i < chunk.byteLength; i++) {
      text += String.fromCharCode(chunk[i]);
    }
    // 将本段数据追加到网页之中
    document.getElementById('content').innerHTML += text;
    // 计算当前进度
    progress += chunk.byteLength;
    console.log(((progress / contentLength) * 100) + '%');
    // 递归处理下一段数据
    return getStream(reader);
  };
};

  上面这样的数据流处理,可以提高网站性能表现,减少内存占用,对于请求大文件或者网速慢的场景相当有用。传统的XMLHTTPRequest对象不支持数据流,所有的数据必须放在缓存里,等到全部拿到后,再一次性吐出来。

三,fetch()

fetch方法的第一个参数可以是URL字符串,也可以是后文要讲到的Request对象实例。Fetch方法返回一个Promise对象,并将一个response对象传给回调函数。

response对象有一个ok属性,如果返回的状态码在200到299之间(即请求成功),这个属性为true,否则为false。因此,判断请求是否成功的代码可以写成下面这样。

fetch('./api/some.json').then(function (response) {
  if (response.ok) {
    response.json().then(function (data) {
      console.log(data);
    });
  } else {
    console.log('请求失败,状态码为', response.status);
  }
}, function(err) {
  console.log('出错:', err);
});

response对象除了json方法,还包含了服务器HTTP回应的元数据.

fetch('users.json').then(function(response) {
  console.log(response.headers.get('Content-Type'));
  console.log(response.headers.get('Date'));
  console.log(response.status);
  console.log(response.statusText);
  console.log(response.type);
  console.log(response.url);
});

上面代码中,response对象有很多属性,其中的response.type属性比较特别,表示HTTP回应的类型,它有以下三个值。

    • basic:正常的同域请求
    • cors:CORS机制下的跨域请求
    • opaque:非CORS机制下的跨域请求,这时无法读取返回的数据,也无法判断是否请求成功

四,Headers

Fetch API引入三个新的对象(也是构造函数):HeadersRequestResponse。其中,Headers对象用来构造/读取HTTP数据包的头信息。

var content = 'Hello World';
var headers = new Headers();
headers.append("Accept", "application/json");
headers.append("Content-Type", "text/plain");
headers.append("Content-Length", content.length.toString());
headers.append("X-Custom-Header", "ProcessThisImmediately");

Headers对象的实例,除了使用append方法添加属性,也可以直接通过构造函数一次性生成。

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

五,Request对象

Request对象用来构造HTTP请求

var req = new Request("/index.html");
req.method // "GET"
req.url // "http://example.com/index.html"

Request对象实例有两个属性是只读的,不能手动设置。一个是referrer属性,表示请求的来源,由浏览器设置,有可能是空字符串。另一个是context属性,表示请求发出的上下文,如果是image,表示是从img标签发出,如果是worker,表示是从worker脚本发出,如果是fetch,表示是从fetch函数发出的。

Request对象的一个很有用的功能,是在其他Request实例的基础上,生成新的Request实例。

var postReq = new Request(req, {method: 'POST'});

六,Response

fetch方法返回Response对象实例,它有以下属性。

    • status:整数值,表示状态码(比如200)
    • statusText:字符串,表示状态信息,默认是“OK”
    • ok:布尔值,表示状态码是否在200-299的范围内
    • headers:Headers对象,表示HTTP回应的头信息
    • url:字符串,表示HTTP请求的网址
    • type:字符串,合法的值有五个basic、cors、default、error、opaque。basic表示正常的同域请求;cors表示CORS机制的跨域请求;error表示网络出错,无法取得信息,status属性为0,headers属性为空,并且导致fetch函数返回Promise对象被拒绝;opaque表示非CORS机制的跨域请求,受到严格限制。

Response对象还有两个静态方法。

    • Response.error() 返回一个type属性为error的Response对象实例
    • Response.redirect(url, status) 返回的Response对象实例会重定向到另一个URL
fetch("https://example.com", init)
.then(function (response) {
// Check that the response is a 200
  if (response.status === 200) {
    alert("Content type: " + response.headers.get('Content-Type'));
  }
});

七,body属性

Request对象和Response对象都有body属性,表示请求的内容。body属性可能是以下的数据类型。

    • ArrayBuffer
    • ArrayBufferView (Uint8Array等)
    • Blob/File
    • string
    • URLSearchParams
    • FormData
var form = new FormData(document.getElementById('login-form'));
fetch("/login", {
  method: "POST",
  body: form
})

上面代码中,Request对象的body属性为表单数据。

Request对象和Response对象都提供以下方法,用来读取body。

    • arrayBuffer()
    • blob()
    • json()
    • text()
    • formData()

注意,上面这些方法都只能使用一次,第二次使用就会报错,也就是说,body属性只能读取一次。Request对象和Response对象都有bodyUsed属性,返回一个布尔值,表示body是否被读取过。

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");
});

  

上面代码中,第二次通过text方法读取Response对象实例的body时,就会报错。

这是因为body属性是一个stream对象,数据只能单向传送一次。这样的设计是为了允许JavaScript处理视频、音频这样的大型文件。

如果希望多次使用body属性,可以使用Response对象和Request对象的clone方法。它必须在body还没有读取前调用,返回一个新的body,也就是说,需要使用几次body,就要调用几次clone方法。

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;
  });
});

  

推荐阅读