fetch的语法及使用

472 阅读8分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


fetch()是 XMLHttpRequest 的升级版,用于 javascript 脚本里面发出 HTTP 请求,浏览器原生提供这个对象。

基本用法

fetch()的功能与 XMLHttpRequest 基本相同,主要的几个差别如下:

  1. fetch()使用 Promise,不使用回调函数,简化了写法;
  2. fetch()采用模块化设计,API 分散则多个对象上(Response 对象、Request 对象、Headers 对象),更合理也更容易管理;
  3. fetch()通过数据流(Stream 对象)处理数据,可以分块读取,有利于提高网站性能表现,减少内存占用,对于请求大文件或者网速慢的场景相当有用。XMLHttpRequest 对象不支持数据流,所有的数据必须放在缓存中,不支持分块读取,必须等到全部拿到后再一次性吐出来。

fetch()接受一个 URL 字符串作为参数,默认向该网址发出 GET 请求,返回一个 Promise 对象。使用方法:

fetch(url).then(...).catch(...)

Response 对象:处理 HTTP 回应

fetch()请求成功后会得到一个 Response 对象,它对应服务器的 HTTP 回应。该对象是一个 Stream 对象,它具备一些异步操作,同时也具备一些同步属性(对应 HTTP 回应的标头信息,可以立即读取)

标头信息属性有如下这些:

Response.ok

该属性返回一个布尔值表示请求是否成功,true 对应 HTTP 请求的状态码 200-299,false 对应其他状态码。

Response.status

该属性返回一个数字,表示 HTTP 回应的状态码;

Response.statusText

该属性返回一个字符串,表示 HTTP 回应的状态信息。

Response.url

该属性返回请求的 URL,如果 URL 存在跳转,该属性返回的是最终的 URL。

Response.type

该属性返回请求的类型。可能的值如下:

  • basic:普通请求,即同源请求。
  • cors:跨域请求。
  • error:网络错误,主要用于 Service Worker。
  • opaque:表示发出的是简单的跨域请求,类似<form>表单的那种跨域请求。
  • opaqueredirect:如果fetch()请求的 redirect 属性设为 manual,就会返回这个值。

Response.redirected

该属性返回一个布尔值,表示请求是否发生过跳转。

判断请求是否成功

注意:fetch()发出请求后,只有网络错误,或者无法连接时,fetch()才会报错,其他情况都不会报错,而是认为请求成功。即使服务器返回状态码为 4XX 或者 5XX,fetch()也不会报错。

所以我们只能通过 Response 对象的属性来判断是否请求成功。

  1. 通过 Response.status 属性,值是否为 2XX
async function fetchText() {
  let response = await fetch("/readme.txt");
  if (response.status >= 200 && response.status < 300) {
    return await response.text();
  } else {
    throw new Error(response.statusText);
  }
}
  1. 通过 Response.ok 属性,值是否为 true
if (response.ok) {
  // 请求成功
} else {
  // 请求失败
}

Response.headers

Response.headers 属性指向一个 Headers 对象,对应 HTTP 回应的所有标头。Headers 对象可以使用for...of循环进行遍历。

Headers 对象提供来以下方法来操作标头:

  • Headers.get():根据指定的键名返回键值。
  • Headers.has():返回一个布尔值,表示是否包含某个标头。
  • Headers.set():将指定的键名设置为新的键名,如果该键名不存在则会添加。
  • Headers.append():添加标头。
  • Headers.delete():删除标头。
  • Headers.keys():返回一个遍历器,可以依次遍历所有键名。
  • Headers.values():返回一个遍历器,可以依次遍历所有键值。
  • Headers.entries():返回一个遍历器,可以依次遍历所有键值对。
  • Headers.forEach():依次遍历标头,每个标头都会执行一次参数函数。

对于 HTTP 回应来说,修改标头意义不大,况且很多标头是只读的,浏览器不允许修改。

let response = await fetch(url);
response.headers.get("Content-Type");
// application/json; charset=utf-8// 键名
for (let key of myHeaders.keys()) {
  console.log(key);
}
​
// 键值
for (let value of myHeaders.values()) {
  console.log(value);
}
​
let response = await fetch(url);
response.headers.forEach((value, key) => console.log(key, ":", value));

Response.text()

Response对象根据服务器返回的不同类型的数据,提供了不同的读取方法。几种方法都是异步的,返回的都是 Promise 对象。必须得等到服务器返回的完整数据。

Response.text()可以用于获取文本数据,如 HTML 文件。

const response = await fetch("/users.html");
const body = await response.text();
document.body.innerHTML = body;

Response.json()

Response.json()主要用于获取服务器返回的 JSON 数据。

Response.formData()

Response.formData()主要用在 Service Worker 里面,拦截用户提交的表单,修改某些数据之后,再提交给服务器。

Response.blob()

Response.blob()用于获取二进制文件。

Response.arrayBuffer()

Response.arrayBuffer()主要用于获取流媒体文件。

Response.clone()

Stream 对象只能读取一次,读取完就没了。即上面五个方法只能使用一个,否则就会报错。

Response 对象提供来Response.clone()方法,创建 Response 独享的副本,实现多次读取。

const response1 = await fetch("flowers.jpg");
const response2 = response1.clone();
​
const myBlob1 = await response1.blob();
const myBlob2 = await response2.blob();
​
image1.src = URL.createObjectURL(myBlob1);
image2.src = URL.createObjectURL(myBlob2);

Response.body

该属性是 Response 对象暴露出的底层接口,返回一个 ReadableStream 对象,供用户操作。它可以用来分块读取内容,应用之一就是显示下载进度。

const response = await fetch("flower.jpg");
const reader = response.body.getReader();
​
while (true) {
  const { done, value } = await reader.read();
​
  if (done) {
    break;
  }
​
  console.log(`Received ${value.length} bytes`);
}

response.body.getReader()方法返回一个遍历器。这个遍历器的 read()方法每次返回一个对象,表示本次读取的内容块。

这个对象的 done 属性是一个布尔值,用来判断有没有读完;value 属性是一个 arrayBuffer 数组,表示内容块的内容,而 value.length 属性是当前块的大小。

fetch()的第二个参数:定制 HTTP 请求

fetch()第一个参数是 URL,还可以接受第二个参数作为配置对象,定制发出 HTTP 请求。

fetch(url, optionObj);

HTTP 请求的方法、标头、数据体都可以在这个对象里面设置。

POST 请求

const response = await fetch(url, {
  method: "POST",
  headers: {
    "Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
  },
  body: "foo=bar&lorem=ipsum",
});
​
const json = await response.json();

以上示例,配置对象用了三个属性:

  • methods:HTTP 请求的方法,POST、DELETE、PUT 都在这个属性设置。
  • headers:一个对象,用来定制HTTP请求的标头。
  • body:POST 请求的数据体。

注意:有些标头不能通过 headers 属性设置,如 Content-length、Cookie、Host 等,它们是由浏览器自动生成,无法修改。

提交 json 数据

const user = { name: "John", surname: "Smith" };
const response = await fetch("/article/fetch/post/user", {
  method: "POST",
  headers: {
    "Content-Type": "application/json;charset=utf-8",
  },
  body: JSON.stringify(user),
});

注意:上例标头Content-Type要设置为application/json;charset=utf-8。因为默认发送的是纯文本,Content-Type的默认值是text/plain;charset=UTF-8

提交表单

const form = document.querySelector("form");
​
const response = await fetch("/users", {
  method: "POST",
  body: new FormData(form),
});

文件上传

如果表单里面有文件选择器,可以用上例的写法,上传的文件包含在整个表单里面,一起提交。

另一种做法就是用脚本添加文件,构造出一个表单,进行上传。

const input = document.querySelector('input[type="file"]');
​
const data = new FormData();
data.append("file", input.files[0]);
data.append("user", "foo");
​
fetch("/avatars", {
  method: "POST",
  body: data,
});

上传二进制文件时,不用修改标头的Content-Type,浏览器会自动设置。

二进制数据上传

fetch()也可以直接上传二进制数据,将 Blob 或 arrayBuffer 数据放在 body 属性里面。

let blob = await new Promise((resolve) =>
  canvasElem.toBlob(resolve, "image/png")
);
​
let response = await fetch("/article/fetch/post/image", {
  method: "POST",
  body: blob,
});

fetch()配置对象的完整 API

const response = fetch(url, {
  method: "GET",
  headers: {
    "Content-Type": "text/plain;charset=UTF-8",
  },
  body: undefined,
  referrer: "about:client",
  referrerPolicy: "no-referrer-when-downgrade",
  mode: "cors",
  credentials: "same-origin",
  cache: "default",
  redirect: "follow",
  integrity: "",
  keepalive: false,
  signal: undefined,
});

fetch()请求的底层用的是 Request()对象的接口,参数完全一样,因此上面的 API 也是 Request()的 API。

cache

该属性指定如何处理缓存,取值如下:

  • default:默认值,先在缓存里面寻找匹配的请求;
  • no-store:直接请求远程服务器,并不更新缓存;
  • reload:直接请求远程服务器,并更新缓存;
  • no-cache:将服务器资源跟本地缓存进行比较,有新的版本才使用服务器资源,否则使用缓存;
  • force-cache:缓存优先,只有不存在缓存的情况下,才请求远程服务器;
  • only-if-cached:只检查缓存,如果缓存里面不存在,将返回 504 错误。

mode

该属性指定请求的模式,取值如下:

  • cors:默认值,允许跨域请求;
  • same-origin:只允许同源请求;
  • no-cors:请求方法只限于 GET、POST 和 HEAD,并且只能使用有限的几个简单标头,不能添加跨域的复杂标头,相当于提交扁担所能发出的请求。

credentials

该属性指定是否发送 Cookie,取值如下:

  • same-origin:默认值,同源请求时发送 Cookie,跨域请求时不发送;
  • include:不管同源请求,还是跨域请求,一律发 Cookie;
  • omit:一律不发送。

signal

该属性用于指定一个 AbortSignal 实例,用于取消fetch()请求。

keep-alive

该属性用于页面卸载时,告诉浏览器中后台保持连接,继续发送数据。

redirect

该属性指定 HTTP 跳转的处理方法,取值如下:

  • follow:默认值,fetch()跟随 HTTP 跳转;
  • error:如果发生跳转,fetch()就报错;
  • manual:fetch()不跟随 HTTP 跳转,但是 response.url 属性会指向新的 URL,response.redirected 属性会变为 true,由开发者自己决定后续如何处理跳转。

integrity

该属性指定一个哈希值,用于检查 HTTP 回应传回的数据是否等于这个预先设定的哈希值。

referrer

该属性用于设定 fetch 请求的 referer 标头。这个属性可以为任意字符串,也可以设为空字符串。

referrerPolicy

该属性用于设定 referer 标头的规则,取值如下:

  • no-referrer-when-downgrade:默认值,总是发送 Referer 标头,除非从 HTTPS 页面请求 HTTP 资源时不发送。
  • no-referrer:不发送 Referer 标头。
  • origin:Referer 标头只包含域名,不包含完整的路径。
  • origin-when-cross-origin:同源请求 Referer 标头包含完整的路径,跨域请求只包含域名。
  • same-origin:跨域请求不发送 Referer,同源请求发送。
  • strict-origin:Referer 标头只包含域名,HTTPS 页面请求 HTTP 资源时不发送 Referer 标头。
  • strict-origin-when-cross-origin:同源请求时 Referer 标头包含完整路径,跨域请求时只包含域名,HTTPS 页面请求 HTTP 资源时不发送该标头。
  • unsafe-url:不管什么情况,总是发送 Referer 标头。

取消 fetch()请求

fetch()请求发送以后,如果中途想要取消,需要使用 AbortController 对象。

let controller = new AbortController();
let signal = controller.signal;

fetch(url, {
  signal: controller.signal,
});

signal.addEventListener("abort", () => console.log("abort!"));

controller.abort(); // 取消

console.log(signal.aborted); // true

先建立 AbortController 实例,然后发送 fetch()请求,配置对象的 signal 属性必须指定接收 AbortController 实例发送的信号controller.signal

controller.abort()方法用于发出取消信号。这时会触发 abort 事件,这个事件可以监听,也可以通过 controller.signal.aborted 属性判断取消信号是否已经发出。