fetch接口

在Fetch API中,最常用的就是fetch()函数。它接收一个URL参数,返回一个promise来处理response。response参数带着一个Response对象。

  1. fetch("/data.json").then(function(res) {
  2. // res instanceof Response == true.
  3. if (res.ok) {
  4. res.json().then(function(data) {
  5. console.log(data.entries);
  6. });
  7. } else {
  8. console.log("Looks like the response wasn't perfect, got status", res.status);
  9. }
  10. }, function(e) {
  11. console.log("Fetch failed!", e);
  12. });

如果是提交一个post请求:

  1. fetch("http://www.example.org/submit.php", {
  2. method: "POST",
  3. headers: {
  4. "Content-Type": "application/x-www-form-urlencoded"
  5. },
  6. body: "firstName=Nikhil&favColor=blue&password=easytoguess"
  7. }).then(function(res) {
  8. if (res.ok) {
  9. alert("Perfect! Your settings are saved.");
  10. } else if (res.status == 401) {
  11. alert("Oops! You are not authorized.");
  12. }
  13. }, function(e) {
  14. alert("Error submitting form!");
  15. });

fetch()函数的参数和传给Request()构造函数的参数保持完全一致,所以可以直接传任意复杂的request请求给fetch()

headers接口

Headers接口是一个简单的多映射的名-值表

  1. let content = "Hello World";
  2. let reqHeaders = new Headers();
  3. reqHeaders.append("Content-Type", "text/plain");
  4. reqHeaders.append("Content-Length", content.length.toString());
  5. reqHeaders.append("X-Custom-Header", "ProcessThisImmediately");

也可以传一个多维数组或者json:

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

Headers的内容可以被检索:

  1. console.log(reqHeaders.has("Content-Type")); // true
  2. console.log(reqHeaders.has("Set-Cookie")); // false
  3. reqHeaders.set("Content-Type", "text/html");
  4. reqHeaders.append("X-Custom-Header", "AnotherValue");
  5. console.log(reqHeaders.get("Content-Length")); // 11
  6. console.log(reqHeaders.getAll("X-Custom-Header")); // ["ProcessThisImmediately", "AnotherValue"]
  7. reqHeaders.delete("X-Custom-Header");
  8. console.log(reqHeaders.getAll("X-Custom-Header")); // []

由于Headers可以在request请求中被发送或者在response请求中被接收,并且规定了哪些参数是可写的,Headers对象有一个特殊的 guard 属性。这个属性没有暴露给Web,但是它影响到哪些内容可以在Headers对象中被改变。
如果使用了一个不合法的HTTP Header属性名,那么Headers的方法通常都抛出 TypeError 异常。如果不小心写入了一个不可写的属性,也会抛出一个 TypeError 异常。除此以外的情况,失败了并不抛出异常。例如:

  1. let res = Response.error();
  2. try {
  3. res.headers.set("Origin", "http://mybank.com");
  4. } catch(e) {
  5. console.log("Cannot pretend to be a bank!");
  6. }

Request接口

Request接口定义了通过HTTP请求资源的request格式。参数需要URLmethodheaders,同时Request也接受一个特定的body,mode,credentials以及cache hints。
最简单的 Request 当然是一个URL,可以通过URL来GET一个资源。

  1. let req = new Request("/index.html");
  2. console.log(req.method); // "GET"
  3. console.log(req.url); // "http://example.com/index.html"

也可以将一个建好的Request对象传给构造函数,这样将复制出一个新的Request。这种用法通常见于ServiceWorkers。

  1. let copy = new Request(req);
  2. console.log(copy.method); // "GET"
  3. console.log(copy.url); // "http://example.com/index.html"

URL以外的其他属性的初始值能够通过第二个参数传给Request构造函数。这个参数是一个json:

  1. let uploadReq = new Request("/uploadImage", {
  2. method: "POST",
  3. headers: {
  4. "Content-Type": "image/png",
  5. },
  6. body: "image data"
  7. });

mode 属性用来决定是否允许跨域请求,以及哪些response属性可读。可选的mode属性值为 same-originno-cors (默认)以及 cors

cors : Cross-Origin HTTP Request

same-origin 模式很简单,如果一个请求是跨域的,那么返回一个简单的error,这样确保所有的请求遵守同源策略。

  1. let arbitraryUrl = document.getElementById("url-input").value;
  2. fetch(arbitraryUrl, { mode: "same-origin" }).then(function(res) {
  3. console.log("Response succeeded?", res.ok);
  4. }, function(e) {
  5. console.log("Please enter a same-origin URL!");
  6. });

no-cors 模式允许来自CDN的脚本、其他域的图片和其他一些跨域资源,但是首先有个前提条件,就是请求的method只能是”HEAD”,”GET”或者”POST”。此外,任何 ServiceWorkers拦截了这些请求,它不能随意添加或者改写任何headers,除了这些。JavaScript不能访问Response中的任何属性,这保证了 ServiceWorkers 不会导致任何跨域下的安全问题而隐私信息泄漏。
cors 模式我们通常用作跨域请求来从第三方提供的API获取数据。这个模式遵守 CORS 协议 。只有有限的一些headers被暴露给Response对象,但是body是可读的。获得一个Flickr的 照片的清单:

  1. let u = new URLSearchParams();
  2. u.append('method', 'flickr.interestingness.getList');
  3. u.append('api_key', '<insert api key here>');
  4. u.append('format', 'json');
  5. u.append('nojsoncallback', '1');
  6. var apiCall = fetch('https://api.flickr.com/services/rest?' + u);
  7. apiCall.then(function(response) {
  8. return response.json().then(function(json) {
  9. // photo is a list of photos.
  10. return json.photos.photo;
  11. });
  12. }).then(function(photos) {
  13. photos.forEach(function(photo) {
  14. console.log(photo.title);
  15. });
  16. });

无法从Headers中读取”Date”属性,因为Flickr在Access-Control-Expose-Headers中设置了不允许读取它。

  1. response.headers.get("Date"); // null

credentials 枚举属性决定了cookies是否能跨域得到。这个属性与XHR的withCredentials标志相同,但是只有三个值,分别是”omit”(默认),”same-origin”以及”include”。

Request对象也可以提供 caching hints 给用户代理。这个属性还在安全复审阶段。Firefox 提供了这个属性,但是它目前还不起作用。

Request还有两个只读的属性与ServiceWorks拦截有关。其中一个是referrer,表示Request的来源,可能为空。另外一个是context,是一个非常大的枚举集合定义了获得的资源的种类,它可能是 image 比如请求来自于img标签,可能是 worker 如果是一个worker脚本,等等。如果使用fetch()函数,这个值是 fetch

Response接口

Response实例通常在fetch()的回调中获得。但是它们也可以用JS构造,不过通常只用于ServiceWorkers。

  1. Response中最常见的属性是status(一个整数默认值是200)和statusText(默认值是”OK”),对应HTTP请求的statusreason。还有一个”ok”属性,当status为2xx的时候它是true。
  2. headers 属性是Response的Headers对象,它是只读的(with guard “response”),url属性是当前Response的来源URL。
  3. Response 也有一个type属性,它的值可能是”basic”,”cors”,”default”,”error”或者”opaque。
    • “basic”: 正常的,同域的请求,包含所有的headers除开”Set-Cookie”和”Set-Cookie2”。
    • “cors”: Response从一个合法的跨域请求获得, 一部分header和body 可读。
    • “error”: 网络错误。Response的status是0,Headers是空的并且不可写。当Response是从Response.error()中得到时,就是这种类型。
    • “opaque”: Response从”no-cors”请求了跨域资源。依靠Server端来做限制。

“error”类型会导致fetch()函数的Promise被reject并回调出一个TypeError。

  1. 还有一些属性只在ServerWorker作用域下有效。以正确的方式 返回一个Response针对一个被ServiceWorkers拦截的Request,可以像下面这样写:
  1. addEventListener('fetch', function(event) {
  2. event.respondWith(new Response("Response body", {
  3. headers: { "Content-Type" : "text/plain" }
  4. });
  5. });

Response有个接收两个可选参数的构造器。第一个参数是返回的body,第二个参数是一个json对象,可以设置statusstatusText以及headers
静态方法Response.error()简单返回一个错误的请求。类似的,Response.redirect(url, status)返回一个跳转URL的请求。

处理body

无论Request还是Response都可能带着body。body可以是各种类型,比较复杂。

body可以是以下任何一种类型的实例:

  • ArrayBuffer //to query
  • ArrayBufferView (Uint8Array and friends)
  • Blob / File
  • 字符串
  • URLSearchParams
  • FormData ——目前不被Gecko和Blink支持,Firefox预计在版本39和Fetch的其他部分一起推出。

此外,Request和Response都为他们的body提供了以下方法,这些方法都返回一个Promise对象。

  1. arrayBuffer()
  2. blob()
  3. json()
  4. text()
  5. formData()

在使用非文本的数据方面,Fetch APIXHR相比提供了极大的便利。

可以通过传body参数来设置Request的body:

  1. let form = new FormData(document.getElementById('login-form'));
  2. fetch("/login", {
  3. method: "POST",
  4. body: form
  5. })

Response的第一个参数是body:

  1. let res = new Response(
  2. new File(["chunk", "chunk"],
  3. "archive.zip",
  4. { type: "application/zip" })
  5. );

Request和Response(通过fetch()方法)都能够自动识别自己的content type ,Request还可以自动设置”Content-Type” header,如果开发者没有设置它的话。

流和克隆

非常重要的一点要说明,那就是Request和Response的body只能被读取一次!它们有一个属性叫bodyUsed,读取一次之后设置为true,就不能再读取了。

  1. let res = new Response("one time use");
  2. console.log(res.bodyUsed); // false
  3. res.text().then(function(v) {
  4. console.log(res.bodyUsed); // true
  5. });
  6. console.log(res.bodyUsed); // true
  7. res.text().catch(function(e) {
  8. console.log("Tried to read already consumed Response");
  9. });

这样设计的目的是为了之后兼容基于流的API,让应用一次消费data,这样就允许了JavaScript处理大文件例如视频,并且可以支持实时压缩和编辑。

有时候,我们希望多次访问body,例如用即将支持的 Cache API 去缓存Request和Response,以便于可以离线使用,Cache要求body能被再次读取。

所以,我们该如何让body能经得起多次读取呢?API提供了一个clone()方法。调用这个方法可以得到一个克隆对象。不过要记得,clone()必须要在读取之前调用,也就是先clone()再读取,

  1. window.addEventListener('fetch', function(evt) {
  2. var sheep = new Response("Dolly");
  3. console.log(sheep.bodyUsed); // false
  4. var clone = sheep.clone();
  5. console.log(clone.bodyUsed); // false
  6. clone.text();
  7. console.log(sheep.bodyUsed); // false
  8. console.log(clone.bodyUsed); // true
  9. evt.respondWith(cache.add(sheep.clone()).then(function(e) {
  10. return sheep;
  11. });
  12. });

未来的改进

为了支持流,Fetch最终将提供可以中断执行读取资源的能力,并且提供可以得到读取进度的API。这些能力在XHR中有,但是想要实现成Promise-based的Fetch API有些麻烦。