Fetch是什么?
fetch是一个JavaScript接口,XMLHttpRequest的一个理想的代替方案,用于跨网络异步获取资源(HTTP 请求)。
一个基本的 fetch 请求设置起来很简单。看看下面的代码:
fetch('http://example.com/movies.json')
.then((response) => {
return response.json();
})
.then((myJson) => {
console.log(myJson);
});
戴雨彤《来迟》: 我这一次,终究还是来的太迟
复习下XMLHttpRequest获取数据(ajax)
步骤:
1.创建XmlHttpRequest对象
2.调用open方法设置基本请求信息
3.设置发送的数据,发送请求
4.注册监听的回调函数
5.拿到返回值,对页面进行更新
//1.创建Ajax对象
if(window.XMLHttpRequest){
var oAjax=new XMLHttpRequest();
}else{
// 兼容ie
var oAjax=new ActiveXObject("Microsoft.XMLHTTP");
}
//2.连接服务器(打开和服务器的连接)
oAjax.open('GET', url, true);
//3.发送
oAjax.send();
//4.接收
oAjax.onreadystatechange=function (){
if(oAjax.readyState==4){
if(oAjax.status==200){
//alert('成功了:'+oAjax.responseText);
fnSucc(oAjax.responseText);
}else{
//alert('失败了');
if(fnFaild){
fnFaild();
}
}
}
};
fetch封装
fetch的参数(feach(url, optionObj))
POST 请求
const response = await fetch(url, {
method: 'POST',
headers: {
"Content-type": "application/json; charset=UTF-8",
},
body: 'foo=bar&lorem=ipsum',
});
const json = await response.json();
配置对象用到了三个属性
method: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)
});
封装一个自己的fetch(不支持表单)
interface IInit extends RequestInit {
body?: BodyInit | null | any;
prefix?: 'api' | 'api1';
}
export default function fetch(url: string, init?: IInit) {
if (!init) init = {};
const prefix = init.prefix ? uPrefix[init.prefix] : uPrefix.api;
if (!init.credentials) init.credentials = 'include'; // 默认,不管同源请求,还是跨域请求,一律发送 Cookie。
if (init.body && typeof init.body === 'object') init.body = JSON.stringify(init.body); // 转化JSON 字符串
if (init.body && !init.method) init.method = 'POST'; // 存在body,默认请求方法POST
if (init.method) init.method = init.method.toUpperCase(); // 兼容下,字符串转换为大写
if (['POST', 'PUT', 'DELETE'].includes(init.method || '')) { // 提交JSON数据, 默认值是'text/plain;charset=UTF-8'
init.headers = Object.assign({}, init.headers || {
'Content-Type': 'application/json',
});
}
init = addCustomHeader(init)
const realUrl = `${prefix}${url}`;
return window
.fetch(realUrl, init)
.then(res => checkStatus(res)) //检验响应码
.then((res) => res.json())
.then((res) => filter(res)) // 根据业务需求,进行对应过滤
.catch(err => console.log(`请求失败! + url: ${realUrl}`, err));
}
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
});
cache
cache属性指定如何处理缓存。可能的取值如下:
default:默认值,先在缓存里面寻找匹配的请求。no-store:直接请求远程服务器,并且不更新缓存。reload:直接请求远程服务器,并且更新缓存。no-cache:将服务器资源跟本地缓存进行比较,有新的版本才使用服务器资源,否则使用缓存。force-cache:缓存优先,只有不存在缓存的情况下,才请求远程服务器。only-if-cached:只检查缓存,如果缓存里面不存在,将返回504错误。
mode
mode属性指定请求的模式。可能的取值如下:
cors:默认值,允许跨域请求。same-origin:只允许同源请求。no-cors:请求方法只限于 GET、POST 和 HEAD,并且只能使用有限的几个简单标头,不能添加跨域的复杂标头,相当于提交表单所能发出的请求。
credentials
credentials属性指定是否发送 Cookie。可能的取值如下:
same-origin:默认值,同源请求时发送 Cookie,跨域请求时不发送。include:不管同源请求,还是跨域请求,一律发送 Cookie。omit:一律不发送。
跨域请求发送 Cookie,需要将credentials属性设为include。
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()就报错。manual:fetch()不跟随 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标头。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属性判断取消信号是否已经发出。
下面是一个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;
}
}
开始打造axios
1、clone克隆demo项目
git clone https://gitee.com/dengruifeng/wabpack-demo
建立 fetch.ts
import { notification } from 'antd';
const window = self.window;
export interface IRes { //与后端约定的格式
code: number;
message: string;
data: any;
}
interface IInit extends RequestInit {
body?: BodyInit | null | any;
}
const checkStatus = (res: Response) => {
if (res.status === 401) {
window.location.href = '/login';
}
if (res.status === 405 || res.status === 403) {
location.href = '/403';
}
if (res.status === 404) {
notification.error({ message: '接口不存在' });
}
return res;
};
const filter = (res: IRes) => {
// if (res.code !== 0 && res.code !== 200) {
// notification.error({
// message: '错误',
// description: res.message || '服务错误,请重试!',
// });
// throw res;
// }
res.data = res;
return res.data; // 返回数据
};
const addCustomHeader = (init?: IInit) => { // 自定义添加头部信息
init = init || {};
init.headers = Object.assign(init?.headers || {}, {
// 'X-SSO-USER': 'admin',
});
return init;
};
export default function fetch(url: string, init?: IInit) {
if (!init) init = {};
// if (!init.credentials) init.credentials = 'include'; // 默认same-origin,include不管同源请求,还是跨域请求,一律发送 Cookie。
if (init.body && typeof init.body === 'object') init.body = JSON.stringify(init.body); // 转化JSON 字符串
if (init.body && !init.method) init.method = 'POST'; // 存在body,默认请求方法POST
if (init.method) init.method = init.method.toUpperCase(); // 兼容下,字符串转换为大写
if (['POST', 'PUT', 'DELETE'].includes(init.method || '')) { // 提交JSON数据, 默认值是'text/plain;charset=UTF-8'
init.headers = Object.assign({}, init.headers || {
'Content-Type': 'application/json',
});
}
init = addCustomHeader(init);
console.log(init);
return window
.fetch(url, init)
.then(res => checkStatus(res)) //检验响应码
.then((res) => res.json())
.then((res) => filter(res)) // 根据业务需求,进行对应过滤
.catch(err => console.log(`请求失败! + url: ${url}`, err));
}
export function formFetch(url: string, init?: IInit) { // 表单fetch,用于上传文件等
if (!init) init = {};
init = addCustomHeader(init)
return window
.fetch(url, init)
.then(res => checkStatus(res))
.then((res) => res.json())
.then((res) => filter(res))
.catch(err => console.log(`请求失败! + url: ${url}`, err));
}
找个免费api测试百度有许多资源
我在这里找
api.uomg.com/doc-rand.im…
建立api接口文件
import fetch from '../utils/fetch';
export const getImgList = (sort: string) => {
return fetch(`https://api.uomg.com/api/rand.img1?sort=${sort}&format=json`);
};
在页面使用
import { Input, Tag } from 'antd';
import { getImgList } from 'api/test';
import React from 'react';
import { Link } from 'react-router-dom';
import './index.less';
export const Page1 = () => {
const [imgurl, setData] = React.useState('');
const [sort, setsort] = React.useState('美女');
React.useEffect(() => {
getData();
}, [sort]);
const getData = () => {
getImgList(sort).then((res: { imgurl: string }) => {
console.log(res);
setData(res.imgurl);
});
};
return (
<div className="test-css">
<h1 className="test-css-home">我是Page1</h1>
<Link to={'/'}>
<Tag>返回首页</Tag>
</Link>
<h2>搜索图片</h2>
<Input.Search
style={{ width: 300 }}
placeholder="输入关键字,如:美女、壁纸等"
onSearch={(value: string) => setsort(value)}
enterButton
/>
<br />
<img src={imgurl} alt="img" />
</div>
);
};
查看完整流程,自行查看项目代码
上传文件(formFetch)
上传的文件包含在整个表单里面,一起提交,文件上传要借助表单
const handleChange = (e: UploadChangeParam) => {
const file = e.fileList[0]?.originFileObj; // 文件流对象
const fileName = e.fileList[0].name; // 文件名
const formData = new FormData();
formData.append('uploadFile', file as any);
formData.append('fileName', fileName);
upload(formData).then((res) => {
console.log(res);
});
};
export const upload = (obj: FormData) => {
return formFetch(`/v3/normal/ecm/package`, {
method: 'POST',
body: obj,
});
};
浏览器会这样展示
直接上传二进制数据
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
官网
问题:
未测试取消fetch()请求
仓库: gitee.com/dengruifeng…