Fetch
Promise<Response> fetch(input[, init])
fetch
是浏览器提供的一个符合Promise规范的请求方法,在fetch出现之前最常用的请求方式是XHR(XMLHttpRequest),通过回调和事件来编写异步请求流程,如果请求之间存在强关联,很容易写出”回调地域“形式的代码,如下:
function requestData(url, data, callback) {
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var data = JSON.parse(xhr.responseText);
callback(null, data);
} else {
callback(new Error('Error loading data'));
}
}
};
xhr.send(data);
}
// 请求第一层数据
requestData('https://api.example.com/data1', {}, function (error, data1) {
if (error) {
console.error('Failed to load data1:', error);
} else {
// 请求第二层数据
requestData(
'https://api.example.com/data2',
{ id: data1[0].id },
function (error, data2) {
if (error) {
console.error('Failed to load data2:', error);
} else {
// 请求第三层数据
requestData(
'https://api.example.com/data3',
{ id: data2[0].id, type: data[1].type },
function (error, data3) {
if (error) {
console.error('Failed to load data3:', error);
} else {
// 继续后续操作
console.log('Data loaded:', data1, data2, data3);
// 可能还有更多嵌套的回调...
}
},
);
}
},
);
}
});
fetch 出现之后,我们可以通过更优雅的基于Promise的异步方式进行请求,如下
async function requestData(url: string, data?: any) {
const response = await fetch(url, {
method: 'POST',
body: JSON.stringify(data),
});
if (!response.ok) {
throw new Error(`Error loading data from ${url}: ${response.statusText}`);
}
return await response.json();
}
async function loadData() {
try {
// 请求第一层数据
const data1 = await requestData('https://api.example.com/data1');
console.log('First data loaded:', data1);
// 使用第一层数据作为参数请求第二层数据
const data2 = await requestData('https://api.example.com/data2', {
id: data1[0].id,
});
console.log('Second data loaded:', data2);
// 使用第二层数据作为参数请求第三层数据
const data3 = await requestData('https://api.example.com/data3', {
id: data2[0].id,
type: data[1].type,
});
console.log('Third data loaded:', data3);
// 处理所有数据
console.log('All data processed:', data1, data2, data3);
} catch (error) {
console.error('Failed to load data:', error);
}
}
fetch 的第二个参数是一个配置对象,可以用来控制请求的一些行为
参数 | 描述 |
---|---|
method | 请求方法,如 GET、POST、PUT 等 |
headers | 请求头信息 |
body | 请求体内容,可以是字符串、FormData 对象或者 Blob 对象 |
mode | 请求模式,如 cors、no-cors 或 same-origin |
credentials | 包含 cookies 在内的凭据模式,如 omit、same-origin 或 include |
cache | 缓存模式,如 default、no-store、reload 或 force-cache |
redirect | 跟随重定向方式, 如 follow, error 或 manual |
referrerPolicy | 控制请求的 referrer 头部发送情况 |
我们可以基于 fetch 来封装一个比较通用的请求方法
const initialHeaders = {
'Content-Type': 'application/json',
}
/**
*
* @param {string} method 请求方法
* @param {string} url 请求地址
* @param {string} body 请求数据
* @Param {Headers} headers 请求头
*/
async function request(method, url, body, headers = initialHeaders) {
// 请求拦截
const response = await fetch(url, {
method,
headers,
body
})
// 响应拦截
if (response.status > 299) {
// error fix
}
return response.json()
}
function get(url, params = {}) {
return request('GET', `${url}?${getQueryString(params)}`)
}
function post(url, body = {}) {
return request('POST', url, JSON.stringify(body))
}
function getQueryString(queryObj) {
return Object.keys(queryObj)
.map((key) => {
const value = queryObj[key];
return `${key}=${value}`;
})
.join('&');
}
Promise规范是fetch比起XHR最优秀的改变,当然这不是全部,fetch实现了一个更为完善的异步请求架构,包括Headers、Request、Response,通过这三部分,可以对请求进行更加精细的控制。
📢 虽然 fetch 返回值是 Promise,但是 HTTP 响应的异常状态(404, 502……非 200 状态段)并不会标记为 reject,而是 resolve 一个 false 值,只有在网络请求失败时才会标记为 reject。
中断请求
fetch 中支持中断请求,我们可以使用AbortController来中断 Web 请求。
fetch 的参数中支持传入一个 signal,只需要要将 AbortController 对象的signal 属性传入,即可使用该AbortController 的 abort 方法来中断这个请求。
let controller;
function fetchFile(url) {
controller = new AbortController()
const signal = controller.signal
fetch(url, {
signal
}).then(res => {
console.log('下载成功')
return res
})
}
function stop() {
if (controller) {
controller.abort()
console.log('下载中止')
}
}
Headers
常用的 headers 直接在 fetch 参数中用对象的形式写入的,其实 Fetch API 还提供了Header 构造器,很不方便但是有些功能限制在 ServiceWorker 中使用。
const initialHeaders = Headers.Headers({
'Content-Type': 'application/json',
})
initialHeaders.set('Authorization', 'xxxx')
initialHeaders.append('Content-Type', 'charset=utf-8')
initialHeaders.get('Accept')
initialHeaders.has('Accept')
- append:给 header 添加一个值或者添加一个不存在的 header 并赋值;
- delete:从 Headers 对象中删除指定 header;
- entries:返回 Headers 对象中所有的键值对;
- get:从 Headers 对象中返回指定 header 的全部值;
- has:从 Headers 对象中返回是否存在指定的 header;
- keys:返回 Headers 对象中所有存在的 header 名;
- set:替换现有的 header 的值,或者添加一个未存在的 header 并赋值;
- values:返回 Headers 对象中所有存在的 header 的值。
set 和 append 的区别在于,set 会覆盖之前的值,append 会在原有的值后追加;
例如
content-type: application/javascript; charset=utf-8
可以通过 append 添加两段或者直接用 set 添加一段
headers.set(‘content-type’, ‘application/javascript; charset=utf-8’)
headers.append(‘Content-Type’, ‘application/javascript’)
headers.append(‘Content-Type’, ‘charset=utf-8’)
Request 和 Response
一个网络请求分为”请求“和”响应“两个过程,这两个过程分别对应Requesth和Response两个模块,通常我们是不需要手动构造的,fetch函数会帮我们构造,一般我们使用的fetch常用API都是基于Response对象的,Request在fetch的使用过程中一般用不到(虽然不怎么用到,还是要了解一下)。
构造一个Request实例所需要的参数和fetch请求的参数是一样的,因为fetch的输入操作就是Request承接的,fetch的参数也可以为一个Request实例。
const request = new Request("https://example.com/api");
fetch(request)
.then((response) => response.json());
fetch的返回值就是Response实例,Response有很多方便开发者处理响应的实例方法
实例方法 | 作用 |
---|---|
arrayBuffer | 将响应体解析为二进制数组 |
blob | 将响应体解析为Blob对象 |
clone | 克隆响应实例对象,因为响应体只能使用一次,克隆之后可以分别进行不同处理 |
formData | 将响应体解析为FormData对象 |
json | 将响应体解析为json对象 |
text | 将响应体解析为纯文本 |
这些方法都是对response.body进行处理,响应体body其实是一个可读流(流的相关知识可以参考“流”),如果不想使用Response提供的实例方法,需要自行实现处理方法,可以通过response.body来进行解析。例如我们可以通过流式加载文本信息到页面上以实现打字机效果
fetch(url)
.then(response => {
const reader = response.body.getReader();
return new ReadableStream({
start(controller) {
const decoder = new TextDecoder();
function read() {
return reader.read().then(({ done, value }) => {
if (done) {
controller.close();
return;
}
controller.enqueue(decoder.decode(value));
setTimeout(read, 50); // 控制展示速度,单位为毫秒
});
}
read();
},
});
})
.then(stream =>
stream.pipeThrough(
new WritableStream({
write(chunk) {
document.getElementById('content').innerText += chunk; // 将内容显示到页面上的某个元素中
},
})
)
);
上面表格列举的Response实例方法,在Request中也有相同的实例方法,区别就是前者用于解析响应体,后者用于解析请求体。
补充:Cache
Cache API 是用于缓存的 API,和Fetch API联系比较紧密,目前的浏览器支持也算是比较完全
Cache 为 Request 和 Response 对象提供了存储机制,最常用的场景是在 ServiceWorker 中使用,虽然Cache 是 ServiceWorker 标准中的,但是 Cache 并不是限制在 ServiceWorker 中使用。
但是,Cache 只限制在 HTTPS 环境下使用。
在使用缓存之前需要使用 CacheStorage(使用 caches 访问) 的 open 方法打开一个命名空间,返回值是一个 Promise 对象,resolve的结果是 cache 缓存对象,同一个域名下可以有多个缓存对象。
caches 有以下方法:
- match:【request, options】检查指定的 request 是否是 cache 对象的键,返回一个resolve 为该匹配的Promise对象;
- has:【cacheName】如果存在 cacheName 的缓存对象则返回一个 Promise对象,resolve 的值为 true,否则为 false;
- open:【cacheName】返回与 cacheName 匹配的 cache 对象(Promise),如果不存在则创建一个缓存对象并返回;
- delete:【cacheName】删除与指定cacheName 匹配的 cache 对象,删除成功返回一个 Promise对象,resolve 值为 true,否则为 false;
- keys:返回caches 所有命名组成的数组。
cache 实例上有以下方法:
- match:【request, options】返回一个 Promise对象,resolve 的结果是跟 Cache 对象匹配的第一个缓存请求;
- matchAll:【request, options】返回一个 Promise对象,resolve 的结果是跟 Cache 对象匹配的缓存请求数组;
- add:【request】抓取一个 url,然后将相应进行缓存
- addAll:【requests】抓取一个url 数组,然后将返回的 response 缓存
- put:【request, response】同时抓取一个 request 和 response 进行缓存
- delete:【request, options】删除 key 为 request 的缓存,返回结果是一个 Promise 对象,删除成功 resolve 的值为 true,如果没有找到 resolve 的值为 false;
- keys:【request, options】返回一个 Promise对象,resolve 的值为 Cache key组成的数组,如果指定了 request则返回对应的Request
具体的使用案例之前在 ServiceWorker 中已经写过了。