Skip to main content

Fetch API 请求数据

一、Fetch 的定义

在传统 AJAX 时代,进行 API 等网络请求都是通过 XMLHttpRequest 或封装后的框架进行网络请求,然而配置和调用方式非常混乱。

Fetch API 提供了一个获取资源的接口,用于访问和操纵 HTTP 的请求和响应,并且基于 Promise,提供了一个全局 fetch() 方法来跨网络异步获取资源。

Fetch 的核心在于对 HTTP 接口的抽象,包括 RequestResponseHeaders 和 Body,以及用于初始化异步请求的 global fetch。得益于 JavaScript 实现的这些抽象好的 HTTP 模块,其他接口能够很方便的使用这些功能。

二、Fetch 的用法

1、基本用法

在用法上,fetch() 接受一个 URL 字符串作为参数,默认向该网址发出 GET 请求,无论请求成功与否都返回一个 Promise 对象,基本用法如下:

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

Fetch 方法返回一个 Promise 对象, 根据 Promise API 的特性, Fetch 可以方便地使用 then 方法将各个处理逻辑串起来, 使用 Promise.resolve() 或 Promise.reject() 方法将分别返回肯定结果的 Promise 或否定结果的 Promise, 从而调用下一个 then 或者 catch。一旦 then 中的语句出现错误, 也将跳到 catch 中。

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

fetch('https://api.github.com/users')
.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';
try {
let response = await fetch(url);
return await response.json();
} catch (error) {
console.log('Request Failed', error);
}
}

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

2、第二个参数:定制 HTTP 请求

fetch(url, optionObj)

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

const response = fetch(url, {
// HTTP 请求的方法,POST、DELETE、PUT 都在这个属性设置
method: "GET",
// headers 是一个对象,用来定制 HTTP 请求的标头。
headers: {
"Content-Type": "text/plain;charset=UTF-8"
},
// POST 请求的数据体
body: undefined,
// 用于设定 fetch() 请求的 referer 标头
referrer: "about:client",
// 用于设定 Referer 标头的规则
referrerPolicy: "no-referrer-when-downgrade",
// 属性指定请求的模式
mode: "cors",
// 属性指定是否发送 Cookie
credentials: "same-origin",
// 属性指定如何处理缓存
cache: "default",
// 属性指定 HTTP 跳转的处理方法
redirect: "follow",
// 指定一个哈希值,用于检查 HTTP 回应传回的数据是否等于这个预先设定的哈希值
integrity: "",
// 用于页面卸载时,告诉浏览器在后台保持连接,继续发送数据
keepalive: false,
// 属性指定一个 AbortSignal 实例,用于取消 fetch() 请求
signal: undefined
});

其中,

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 标头。

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

cors:默认值,允许跨域请求。

same-origin:只允许同源请求。

no-cors:请求方法只限于 GET、POST 和 HEAD,并且只能使用有限的几个简单标头,不能添加跨域的复杂标头,相当于提交表单所能发出的请求。

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

same-origin:默认值,同源请求时发送 Cookie,跨域请求时不发送。

include:不管同源请求,还是跨域请求,一律发送 Cookie。

omit:一律不发送。

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

default:默认值,先在缓存里面寻找匹配的请求。

no-store:直接请求远程服务器,并且不更新缓存。

reload:直接请求远程服务器,并且更新缓存。

no-cache:将服务器资源跟本地缓存进行比较,有新的版本才使用服务器资源,否则使用缓存。

force-cache:缓存优先,只有不存在缓存的情况下,才请求远程服务器。

only-if-cached:只检查缓存,如果缓存里面不存在,将返回 504 错误。

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

follow:默认值,fetch()跟随 HTTP 跳转。

error:如果发生跳转,fetch()就报错。

manual:fetch()不跟随 HTTP 跳转,但是response.url属性会指向新的 URL,response.redirected属性会变为true,由开发者自己决定后续如何处理跳转。

3、取消 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 属性判断取消信号是否已经发出。

下面是一个 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;
}
}

三、与 AJAX/Axios 的对比

1、AJAX 请求 JSON 数据示例

var xhr = new XMLHttpRequest();
xhr.open('GET', url / file, true);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
var data = xhr.responseText;
console.log(data);
};
xhr.onerror = function () {
console.log("Oh, error");
};
xhr.send();
}

2、Fetch 请求 JSON 数据示例

fetch(url).then(response => response.json())   // 解析为可读数据
.then(data => console.log(data)) // 执行结果是 resolve 就调用 then 方法
.catch(err => console.log("Oh, error", err)) // 执行结果是 reject 就调用 catch 方法

从两者对比来看,Fetch 代码精简许多,业务逻辑更清晰明了,使得代码易于维护,可读性更高。

3、Fetch 对比 AJAX 的优点

  • Fetch 使用 Promise,不使用回调函数,使得写法更简洁,而且支持 async/await。
  • Fetch 采用模块化设计,API 合理的分散在多个对象上(Response 对象Request 对象Headers 对象);而 XMLHttpRequest 的 API 输入、输出、状态都在同一个接口管理,容易写出混乱的代码。
  • Fetch 通过数据流(Stream 对象)处理数据,可以分块读取,有利于提高网站性能表现,减少内存占用。XMLHTTPRequest 对象不支持数据流,所有的数据必须放在缓存里,不支持分块读取,必须等待全部拿到后,再一次性吐出来。

4、Fetch 与 AJAX/Axios 的关系

  • AJAX 是一种代表异步 JavaScript + XML 的模型(技术合集),所以 Fetch 也是 AJAX 的一个子集
  • 在之前,我们常说的 AJAX 默认是指以 XHR 为核心的技术合集,而在有了 Fetch 之后,AJAX 不再单单指 XHR 了,我们将以 XHR 为核心的 AJAX 技术称作传统 AJAX。
  • Axios 属于传统 AJAX(XHR)的子集,因为它是基于 XHR 进行的封装。

换句话说,AJAX 是异步请求的统称,Axios 和 Fetch 都是利用 Promise 封装实现的 AJAX(Axios 是对 XMLHttpRequest 的封装,而 Fetch 是一种新的获取资源的接口方式,并非对 XMLHttpRequest 的封装),另外 Fetch 是浏览器亲生的,而 Axios 是别人家的孩子。

四、Fetch 的应用

<div class="container">
<h1>Fetch Api sandbox</h1>
<button id="button1">请求本地文本数据</button>
<button id="button2">请求本地 JSON 数据</button>
<button id="button3">请求网络接口</button>
<br><br>
<div id="output"></div>
</div>
<script src="app.js"></script>

1、请求本地文本数据

本地有一个 test.txt 文档,通过以下代码就可以获取其中的数据,并且显示在页面上。

document.getElementById('button1').addEventListener('click', getText);

function getText() {
fetch("test.txt")
.then((res) => res.text()) // 注意:此处是res.text()
.then(data => {
console.log(data);
document.getElementById('output').innerHTML = data;
})
.catch(err => console.log(err));
}

2、请求本地 JSON 数据

本地有个 posts.json 数据,与请求本地文本不同的是,得到数据后还要用 forEach 遍历,最后呈现在页面上。

document.getElementById('button2').addEventListener('click', getJson);

function getJson() {
fetch("posts.json")
.then((res) => res.json())
.then(data => {
console.log(data);
let output = '';
data.forEach((post) => {
output += `<li>${post.title}</li>`;
})
document.getElementById('output').innerHTML = output;
})
.catch(err => console.log(err));
}

3、请求网络接口

获取 https://api.github.com/users 中的数据,做法与获取本地 JSON 的方法类似,得到数据后,同样要经过处理。

document.getElementById('button3').addEventListener('click', getExternal);

function getExternal() {
fetch("https://api.github.com/users")
.then((res) => res.json())
.then(data => {
console.log(data);
let output = '';
data.forEach((user) => {
output += `<li>${user.login}</li>`;
})
document.getElementById('output').innerHTML = output;
})
.catch(err => console.log(err));
}