跳至主要內容

JavaScript使用Fetch

soulballad扩展前端JavaScriptJavaScript约 3498 字大约 12 分钟

Fetch APIopen in new window 提供了一个 JavaScript接口,用于访问和操纵HTTP管道的部分,例如请求和响应。它还提供了一个全局 fetch()open in new window方法,该方法提供了一种简单,合理的方式来跨网络异步获取资源。

这种功能以前是使用 XMLHttpRequestopen in new window实现的。Fetch提供了一个更好的替代方法,可以很容易地被其他技术使用,例如 Service Workersopen in new window。Fetch还提供了单个逻辑位置来定义其他HTTP相关概念,例如CORS和HTTP的扩展。

请注意,fetch规范与jQuery.ajax()主要有两种方式的不同,牢记:

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

进行 fetch 请求

一个基本的 fetch请求设置起来很简单。看看下面的代码:

fetch('http://example.com/movies.json')
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(myJson);
  });

这里我们通过网络获取一个JSON文件并将其打印到控制台。最简单的用法是只提供一个参数用来指明想fetch()到的资源路径,然后返回一个包含响应结果的promise(一个 Responseopen in new window 对象)。

当然它只是一个 HTTP 响应,而不是真的JSON。为了获取JSON的内容,我们需要使用 json()open in new window方法(在Bodyopen in new windowmixin 中定义,被 Requestopen in new windowResponseopen in new window 对象实现)。

注意:Body mixin 还有其他相似的方法,用于获取其他类型的内容。参考 Bodyopen in new window

最好使用符合内容安全策略 (CSP)open in new window的链接而不是使用直接指向资源地址的方式来进行Fetch的请求。

支持的请求参数参考open in new window

fetch() 接受第二个可选参数,一个可以控制不同配置的 init 对象:

参考 fetch()open in new window,查看所有可选的配置和更多描述。

// Example POST method implementation:

postData('http://example.com/answer', {answer: 42})
  .then(data => console.log(data)) // JSON from `response.json()` call
  .catch(error => console.error(error))

function postData(url, data) {
  // Default options are marked with *
  return fetch(url, {
    body: JSON.stringify(data), // must match 'Content-Type' header
    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
    credentials: 'same-origin', // include, same-origin, *omit
    headers: {
      'user-agent': 'Mozilla/4.0 MDN Example',
      'content-type': 'application/json'
    },
    method: 'POST', // *GET, POST, PUT, DELETE, etc.
    mode: 'cors', // no-cors, cors, *same-origin
    redirect: 'follow', // manual, *follow, error
    referrer: 'no-referrer', // *client, no-referrer
  })
  .then(response => response.json()) // parses response to JSON
}

发送带凭据的请求

为了让浏览器发送包含凭据的请求(即使是跨域源),要将credentials: 'include'添加到传递给 fetch()方法的init对象。

fetch('https://example.com', {
  credentials: 'include'  
})

如果你只想在请求URL与调用脚本位于同一起源处时发送凭据,请添加credentials: 'same-origin'

// The calling script is on the origin 'https://example.com'

fetch('https://example.com', {
  credentials: 'same-origin'  
})

要改为确保浏览器不在请求中包含凭据,请使用credentials: 'omit'

fetch('https://example.com', {
  credentials: 'omit'  
})

上传 JSON 数据

使用 fetch()open in new window POST JSON数据

var url = 'https://example.com/profile';
var data = {username: 'example'};

fetch(url, {
  method: 'POST', // or 'PUT'
  body: JSON.stringify(data), // data can be `string` or {object}!
  headers: new Headers({
    'Content-Type': 'application/json'
  })
}).then(res => res.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));

上传文件

可以通过HTML<input type="file" />元素,FormData()open in new windowfetch()open in new window上传文件。

var formData = new FormData();
var fileField = document.querySelector("input[type='file']");

formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);

fetch('https://example.com/profile/avatar', {
  method: 'PUT',
  body: formData
})
.then(response => response.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));

上传多个文件

可以通过HTML<input type="file" mutiple/>元素,FormData()open in new windowfetch()open in new window上传文件。

var formData = new FormData();
var photos = document.querySelector("input[type='file'][multiple]");

formData.append('title', 'My Vegas Vacation');
formData.append('photos', photos.files);

fetch('https://example.com/posts', {
  method: 'POST',
  body: formData
})
.then(response => response.json())
.then(response => console.log('Success:', JSON.stringify(response)))
.catch(error => console.error('Error:', error));

检测请求是否成功

如果遇到网络故障,fetch()open in new window promise 将会 reject,带上一个 TypeErroropen in new window 对象。虽然这个情况经常是遇到了权限问题或类似问题——比如 404 不是一个网络故障。想要精确的判断 fetch() 是否成功,需要包含 promise resolved 的情况,此时再判断 Response.okopen in new window 是不是为 true。类似以下代码:

fetch('flowers.jpg').then(function(response) {
  if(response.ok) {
    return response.blob();
  }
  throw new Error('Network response was not ok.');
}).then(function(myBlob) { 
  var objectURL = URL.createObjectURL(myBlob); 
  myImage.src = objectURL; 
}).catch(function(error) {
  console.log('There has been a problem with your fetch operation: ', error.message);
});

自定义请求对象

除了传给 fetch() 一个资源的地址,你还可以通过使用 Request()open in new window 构造函数来创建一个 request 对象,然后再作为参数传给 fetch()

var myHeaders = new Headers();

var myInit = { method: 'GET',
               headers: myHeaders,
               mode: 'cors',
               cache: 'default' };

var myRequest = new Request('flowers.jpg', myInit);

fetch(myRequest).then(function(response) {
  return response.blob();
}).then(function(myBlob) {
  var objectURL = URL.createObjectURL(myBlob);
  myImage.src = objectURL;
});

Request()fetch() 接受同样的参数。你甚至可以传入一个已存在的 request 对象来创造一个拷贝:

var anotherRequest = new Request(myRequest,myInit);

这个很有用,因为 request 和 response bodies 只能被使用一次(译者注:这里的意思是因为设计成了 stream 的方式,所以它们只能被读取一次)。创建一个拷贝就可以再次使用 request/response 了,当然也可以使用不同的 init 参数。

注意clone()open in new window 方法也可以用于创建一个拷贝。它在语义上有一点不同于其他拷贝的方法。其他方法(比如拷贝一个 response)中,如果 request 的 body 已经被读取过,那么将执行失败,然而 clone() 则不会失败。

Headers

使用 Headersopen in new window 的接口,你可以通过 Headers()open in new window 构造函数来创建一个你自己的 headers 对象。一个 headers 对象是一个简单的多名值对:

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

也可以传一个多维数组或者对象字面量:

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

它的内容可以被获取:

console.log(myHeaders.has("Content-Type")); // true
console.log(myHeaders.has("Set-Cookie")); // false
myHeaders.set("Content-Type", "text/html");
myHeaders.append("X-Custom-Header", "AnotherValue");

console.log(myHeaders.get("Content-Length")); // 11
console.log(myHeaders.getAll("X-Custom-Header")); // ["ProcessThisImmediately", "AnotherValue"]

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

虽然一些操作只能在 ServiceWorkersopen in new window 中使用,但是它提供了更方便的操作 Headers 的 API。

如果使用了一个不合法的HTTP Header属性名,那么Headers的方法通常都抛出 TypeError 异常。如果不小心写入了一个不可写的属性,也会抛出一个 TypeError 异常。除此以外的情况,失败了并不抛出异常。例如:

var myResponse = Response.error();
try {
  myResponse.headers.set("Origin", "http://mybank.com");
} catch(e) {
  console.log("Cannot pretend to be a bank!");
}

最佳实践是在使用之前检查 content type 是否正确,比如:

fetch(myRequest).then(function(response) {
  if(response.headers.get("content-type") === "application/json") {
    return response.json().then(function(json) {
      // process your JSON further
    });
  } else {
    console.log("Oops, we haven't got JSON!");
  }
});

Guard

由于 Headers 可以在 request 请求中被发送或者在 response 请求中被接收,并且规定了哪些参数是可写的,Headers 对象有一个特殊的 guard 属性。这个属性没有暴露给 Web,但是它影响到哪些内容可以在 Headers 对象中被操作。

可能的值如下:

注意:你不可以添加或者修改一个 guard 属性是 request 的 Request Headers 的 Content-Length 属性。同样地,插入 Set-Cookie 属性到一个 response headers 是不允许的,因此 ServiceWorkers 是不能给合成的 Response 的 headers 添加一些 cookies。

Response 对象

如上述, Responseopen in new window 实例是在 fetch() 处理完promises之后返回的。

你会用到的最常见的response属性有:

它的实例也可用通过 JavaScript 来创建, 但只有在ServiceWorkersopen in new window中才真正有用,当使用respondWith()open in new window方法并提供了一个自定义的response来接受request时:

var myBody = new Blob();

addEventListener('fetch', function(event) {
  event.respondWith(new Response(myBody, {
    headers: { "Content-Type" : "text/plain" }
  });
});

Response()open in new window 构造方法接受两个可选参数—response的数据体和一个初始化对象(与Request()open in new window所接受的init参数类似.)

注意: 静态方法error()open in new window只是返回了一个错误的response. 与此类似地, redirect()open in new window 只是返回了一个可以重定向至某URL的response. 这些也只与Service Workers才有关。

Body

不管是请求还是响应都能够包含body对象. body也可以是以下任意类型的实例.

Bodyopen in new window 类定义了以下方法 (这些方法都被 Requestopen in new windowResponseopen in new window所实现)以获取body内容. 这些方法都会返回一个被解析后的Promiseopen in new window对象和数据.

比起XHR来,这些方法让非文本化的数据使用起来更加简单。

请求体可以由传入body参数来进行设置:

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

request和response(包括fetch() 方法)都会试着自动设置Content-Type。如果没有设置Content-Type值,发送的请求也会自动设值。

特性检测

Fetch API 的支持情况,可以通过检测Headersopen in new window, Requestopen in new window, Responseopen in new windowfetch()open in new window是否在Windowopen in new windowWorkeropen in new window 域中。例如:

if(self.fetch) {
    // run my fetch request here
} else {
    // do something with XMLHttpRequest?
}

来源:JavaScript使用 Fetchopen in new window

上次编辑于:
贡献者: soulballad