5

Fetch API 教程

 3 years ago
source link: http://www.ruanyifeng.com/blog/2020/12/fetch-tutorial.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

fetch() 是 XMLHttpRequest 的升级版,用于在 JavaScript 脚本里面发出 HTTP 请求。

浏览器原生提供这个对象。本文详细介绍它的用法。

qMfmErn.jpg!mobile

一、基本用法

fetch() 的最大特点,就是使用 Promise,不使用回调函数。因此大大简化了 API,写起来更简洁。

fetch() 接受一个 URL 字符串作为参数,默认向该网址发出 GET 请求,返回一个 Promise 对象。它的基本用法如下。

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

下面是一个例子,从服务器获取 JSON 数据。

fetch('https://api.github.com/users/ruanyf')
  .then(response => response.json())
  .then(json => console.log(json))
  .catch(err => console.log('Request Failed', err));

上面示例中, fetch() 接收到的 response 是一个 Stream 对象response.json() 是一个异步操作,取出所有内容,并将其转为 JSON 对象。

Promise 可以使用 await 语法改写,使得语义更清晰。

async function getJSON() {
  let url = 'https://api.github.com/users/ruanyf';
  try {
    let response = await fetch(url);
    return await response.json();
  } catch (error) {
    console.log('Request Failed', error);
  }
}

上面示例中, await 语句必须放在 try...catch 里面,这样才能捕捉异步操作中可能发生的错误。

后文都采用 await 的写法,不使用 .then() 的写法。

二、Response 对象:处理 HTTP 回应

2.1 Response 对象的同步属性

fetch() 请求成功以后,得到的是一个 Response 对象 。它对应服务器的 HTTP 回应。

const response = await fetch(url);

前面说过,Response 包含的数据通过 Stream 接口异步读取,但是它还包含一些同步属性,对应 HTTP 回应的标头信息(Headers),可以立即读取。

async function fetchText() {
  let response = await fetch('/readme.txt');
  console.log(response.status); 
  console.log(response.statusText);
}

上面示例中, response.statusresponse.statusText 就是 Response 的同步属性,可以立即读取。

标头信息属性有下面这些。

Response.ok

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

Response.status

Response.status 属性返回一个数字,表示 HTTP 回应的状态码(例如200,表示成功请求)。

Response.statusText

Response.statusText 属性返回一个字符串,表示 HTTP 回应的状态信息(例如请求成功以后,服务器返回"OK")。

Response.url

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

Response.type

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

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

Response.redirected

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

2.2 判断请求是否成功

fetch() 发出请求以后,有一个很重要的注意点:只有网络错误,或者无法连接时, fetch() 才会报错,其他情况都不会报错,而是认为请求成功。

这就是说,即使服务器返回的状态码是 4xx 或 5xx, fetch() 也不会报错(即 Promise 不会变为 rejected 状态)。

只有通过 Response.status 属性,得到 HTTP 回应的真实状态码,才能判断请求是否成功。请看下面的例子。

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

上面示例中, response.status 属性只有等于 2xx (200~299),才能认定请求成功。这里不用考虑网址跳转(状态码为 3xx),因为 fetch() 会将跳转的状态码自动转为 200。

另一种方法是判断 response.ok 是否为 true

if (response.ok) {
  // 请求成功
} else {
  // 请求失败
}

2.3 Response.headers 属性

Response 对象还有一个 Response.headers 属性,指向一个 Headers 对象 ,对应 HTTP 回应的所有标头。

Headers 对象可以使用 for...of 循环进行遍历。

const response = await fetch(url);

for (let [key, value] of response.headers) { 
  console.log(`${key} : ${value}`);  
}

// 或者
for (let [key, value] of response.headers.entries()) { 
  console.log(`${key} : ${value}`);  
}

Headers 对象提供了以下方法,用来操作标头。

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

上面的有些方法可以修改标头,那是因为继承自 Headers 接口。对于 HTTP 回应来说,修改标头意义不大,况且很多标头是只读的,浏览器不允许修改。

这些方法中,最常用的是 response.headers.get() ,用于读取某个标头的值。

let response =  await  fetch(url);  
response.headers.get('Content-Type')
// application/json; charset=utf-8

Headers.keys()Headers.values() 方法用来分别遍历标头的键名和键值。

// 键名
for(let key of myHeaders.keys()) {
  console.log(key);
}

// 键值
for(let value of myHeaders.keys()) {
  console.log(value);
}

Headers.forEach() 方法也可以遍历所有的键值和键名。

let response = await fetch(url);
response.headers.forEach(
  (value, key) => console.log(key, ':', value)
);

2.4 读取内容的方法

Response 对象根据服务器返回的不同类型的数据,提供了不同的读取方法。

response.text()
response.json()
response.blob()
response.formData()
response.arrayBuffer()

上面5个读取方法都是异步的,返回的都是 Promise 对象。必须等到异步操作结束,才能得到服务器返回的完整数据。

response.text()

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() 用于获取二进制文件。

const response = await fetch('flower.jpg');
const myBlob = await response.blob();
const objectURL = URL.createObjectURL(myBlob);

const myImage = document.querySelector('img');
myImage.src = objectURL;

上面示例读取图片文件 flower.jpg ,显示在网页上。

response.arrayBuffer()

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

const audioCtx = new window.AudioContext();
const source = audioCtx.createBufferSource();

const response = await fetch('song.ogg');
const buffer = await response.arrayBuffer();

const decodeData = await audioCtx.decodeAudioData(buffer);
source.buffer = buffer;
source.connect(audioCtx.destination);
source.loop = true;

上面示例是 response.arrayBuffer() 获取音频文件 song.ogg ,然后在线播放的例子。

2.5 Response.clone()

Stream 对象只能读取一次,读取完就没了。这意味着,前一节的五个读取方法,只能使用一个,否则会报错。

let text =  await response.text();
let json =  await response.json();  // 报错

上面示例先使用了 response.text() ,就把 Stream 读完了。后面再调用 response.json() ,就没有内容可读了,所以报错。

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

const response1 = await fetch('flowers.jpg');
const response2 = response1.clone();

const myBlob1 = await response1.blob();
const myBlob2 = await response2.blog();

image1.src = URL.createObjectURL(myBlob1);
image2.src = URL.createObjectURL(myBlob2);

上面示例中, response.clone() 复制了一份 Response 对象,然后将同一张图片读取了两次。

Response 对象还有一个 Response.redirect() 方法,用于将 Response 结果重定向到指定的 URL。该方法一般只用在 Service Worker 里面,这里就不介绍了。

2.6 Response.body 属性

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)

上面命令的 optionObj 就是第二个参数。

HTTP 请求的方法、标头、数据体都在这个对象里面设置。下面是一些示例。

(1)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();

上面示例中,配置对象用到了三个属性。

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

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

(2)提交 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'

(3)提交表单

const form = document.querySelector('form');

const response = await fetch('/users', {
  method: 'POST',
  body: new FormData(form)
})

(4)文件上传

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

另一种方法是用脚本添加文件,构造出一个表单,进行上传,请看下面的例子。

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 ,浏览器会自动设置。

(5)直接上传二进制数据

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

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。

这些属性里面, headersbodymethod 前面已经给过示例了,下面是其他属性的介绍。

cache

cache 属性指定如何处理缓存。可能的取值如下:

default
no-store
reload
no-cache
force-cache
only-if-cached

mode

mode 属性指定请求的模式。可能的取值如下:

cors
same-origin
no-cors

credentials

credentials 属性指定是否发送 Cookie。可能的取值如下:

same-origin
include
omit

跨域请求发送 Cookie,需要将 credentials 属性设为 true

fetch('http://another.com', {
  credentials: "include"
});

signal

signal 属性指定一个 AbortSignal 实例,用于取消 fetch() 请求,详见下一节。

keepalive

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

一个典型的场景就是,用户离开网页时,脚本向服务器提交一些用户行为的统计信息。这时,如果不用 keepalive 属性,数据可能无法发送,因为浏览器已经把页面卸载了。

window.onunload = function() {
  fetch('/analytics', {
    method: 'POST',
    body: "statistics",
    keepalive: true
  });
};

redirect

redirect 属性指定 HTTP 跳转的处理方法。可能的取值如下:

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

integrity

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

比如,下载文件时,检查文件的 SHA-256 哈希值是否相符,确保没有被篡改。

fetch('http://site.com/file', {
  integrity: 'sha256-abcdef'
});

referrer

referrer 属性用于设定 fetch() 请求的 referer 标头。

这个属性可以为任意字符串,也可以设为空字符串(即不发送 referer 标头)。

fetch('/page', {
  referrer: ''
});

referrerPolicy

referrerPolicy 属性用于设定 Referer 标头的规则。可能的取值如下:

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

五、取消 fetch() 请求

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

let controller = new AbortController();
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 事件,这个事件可以监听,也可以通过 signal.aborted 属性判断取消信号是否已经发出。

下面是一个1秒后自动取消请求的例子。

let controller = new AbortController();
setTimeout(() => controller.abort(), 1000);

try {
  let response = await fetch('/long-operation', {
    signal: controller.signal
  });
} catch(err) {
  if (err.name == 'AbortError') {
    console.log('Aborted!');
  } else {
    throw err;
  }
}

六、参考链接

(完)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK