一、概念
1. fetch基础概念
Fetch API 提供了一个获取资源的接口(包括跨域请求)。提供了对 Request 和 Response (以及其他与网络请求有关的)对象的通用定义,使之今后可以被使用到更多地应用场景中:无论是service workers、Cache API、又或者是其他处理请求和响应的方式,甚至是任何一种需要你自己在程序中生成响应的方式。
window.fetch是基于Promise设计的,更强大。- 现在所有的现代浏览器都在本地实现fetch函数,如果使用fetch时本身报错,那就是你使用的浏览器不支持。
2. fetch语法
语法
Promise fetch(url, param);
参数
url: 定义要获取的资源
param[可选]: 一个配置项对象,包括所有对请求的设置。可选的参数有:
- method: 请求使用的方法,如 GET、POST。
- headers: 请求的头信息,形式为 Headers 的对象或包含 ByteString 值的对象字面量。
- body: 请求的 body 信息:可能是一个 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息。
- mode: 请求的模式,如 cors、 no-cors 或者 same-origin。
- credentials: 请求的 credentials,如 omit、same-origin 或者 include。为了在当前域名内自动发送 cookie
- cache: 请求的 cache 模式: default 、 no-store 、 reload 、 no-cache 、 force-cache 或者 only-if-cached 。
请注意,fetch规范与jQuery.ajax()主要有两种方式的不同,牢记:
- 当接收到一个代表错误的 HTTP 状态码时,从 fetch()返回的 Promise 不会被标记为 reject, 即使该 HTTP 响应的状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 resolve 的返回值的 ok 属性设置为 false ),仅当网络故障时或请求被阻止时,才会标记为 reject。
- 默认情况下,fetch 不会从服务端发送或接收任何 cookies, 如果站点依赖于用户 session,则会导致未经认证的请求(要发送 cookies,必须设置 credentials 选项)。自从2017年8月25日后,默认的credentials政策变更为same-originFirefox也在61.0b13中改变默认值
3. fetch使用
再使用之前,你先把程序跑起来,看看效果~~
Koa2服务和React服务,存在跨域限制。解决方案:
- 开发环境下:配置
proxy代理 - 生产环境下:
Nginx代理
// Koa2 中设置
// 利用 koa2-cors 解决跨域
npm install --save koa2-cors
var Koa = require('koa');
var cors = require('koa2-cors');
var app = new Koa();
// 方法1
app.use(cors({
"Access-Control-Allow-Origin": "*"
}));
// 方法2
app.use(cors({
origin: function(ctx) {
if (ctx.url === '/test') {
return false;
}
return '*';
},
exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
maxAge: 5,
credentials: true,
allowMethods: ['GET', 'POST', 'DELETE'],
allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
}));
// react 中请求
useEffect(()=>{
fetch('http://127.0.0.1:3000/string',{method:"POST"})
.then(function(response) {
console.log(response);
return response.json(); // 这只是一个 HTTP 响应,而不是真的JSON。为了获取JSON的内容,需要使用 json()方法
})
.then(function(myJson) {
console.log(myJson);
});
})
"proxy": "http://127.0.0.1:3000/"
useEffect(()=>{
fetch('http://127.0.0.1:3000/string',{
method:"POST",
body: JSON.stringify(data), // 将参数以JSON字符串的形式传递给后端
credentials: 'include', // 带凭据,不论是不是跨域的请求,总是发送请求资源域在本地的 cookies、 HTTP Basic authentication 等验证信息.
headers: {// 定义都以JSON字符串格式传递
'Accept': 'application/json', // 接口要返回给客户端的数据格式,
'Content-Type': 'application/json' // 客户端发送给服务器端的数据格式
},
mode: 'cors', // 支持跨域 no-cors, cors, *same-origin
})
.then(function(response) {
console.log(response);
return response.json(); // 这只是一个 HTTP 响应,而不是真的JSON。为了获取JSON的内容,需要使用 json()方法
})
.then(function(myJson) {
console.log(myJson);
});
})
(2). 上传文件
可以通过HTML<input type="file" />元素,FormData() 和fetch()上传文件。
var formData = new FormData();
var fileField = document.querySelector("input[type='file']");
formData.append('username', 'abc123');
formData.append('avatar', fileField.files[0]);
fetch('https://example.com/profile/avatar', {
method: 'PUT',
body: formData
})
.then(response => response.json())
.catch(error => console.error('Error:', error))
.then(response => console.log('Success:', response));
(3). 上传多个文件
可以通过HTML<input type="file" mutiple/>元素,FormData() 和fetch()上传文件。
var formData = new FormData();
var photos = document.querySelector("input[type='file'][multiple]");
formData.append('title', 'My Vegas Vacation');
// formData 只接受文件、Blob 或字符串,不能直接传数组,所以必须循环嵌入
for (let i = 0; i < photos.files.length; i++) {
formData.append('photo', photos.files[i]);
}
fetch('https://example.com/posts', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(response => console.log('Success:', JSON.stringify(response)))
.catch(error => console.error('Error:', error));
(4). 检测请求是否成功 如果遇到网络故障,fetch() promise 将会 reject,带上一个 TypeError 对象。虽然这个情况经常是遇到了权限问题或类似问题——比如 404 不是一个网络故障。想要精确的判断 fetch() 是否成功,需要包含 promise resolved 的情况,此时再判断 Response.ok 是不是为 true。类似以下代码:
fetch('flowers.jpg').then(function(response) {
if(response.ok) {
return response.blob();
}
throw new Error('Network response was not ok.');
}).then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
myImage.src = objectURL;
}).catch(function(error) {
console.log('There has been a problem with your fetch operation: ', error.message);
});
二、whatwg-fetch
1. whatwg-fetch简介
fetch挂在在BOM中,可以直接在谷歌浏览器中使用。如果不支持fetch也没有问题,可以使用第三方的ployfill来实现fetch:whatwg-fetch。
whatwg-fetch,是基于window.fetch的polyfill(一个全局的垫片,我认为和es6引入时的意思类似),它实现了标准Fetch规范的一个子集。
安装
npm install whatwg-fetch --save
import 'whatwg-fetch'
window.fetch(...)
// webpack 中配置
entry: ['whatwg-fetch', ...]
2. whatwg-fetch源码解析
whatwg-fetch就是基于Promise,对XMLHttpRequest的封装,能够达到链式调用的效果。源码地址
...
// 对 Headers 封装
export function Headers(headers) {
this.map = {}
if (headers instanceof Headers) {
headers.forEach(function(value, name) {
this.append(name, value)
}, this)
} else if (Array.isArray(headers)) {
headers.forEach(function(header) {
this.append(header[0], header[1])
}, this)
} else if (headers) {
Object.getOwnPropertyNames(headers).forEach(function(name) {
this.append(name, headers[name])
}, this)
}
}
// 对 fetch 的封装
export function fetch(input, init) {
return new Promise(function(resolve, reject) {
var request = new Request(input, init)
if (request.signal && request.signal.aborted) {
return reject(new DOMException('Aborted', 'AbortError'))
}
var xhr = new XMLHttpRequest()
function abortXhr() {
xhr.abort()
}
xhr.onload = function() {
var options = {
status: xhr.status,
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders() || '')
}
options.url = 'responseURL' in xhr ? xhr.responseURL : options.headers.get('X-Request-URL')
var body = 'response' in xhr ? xhr.response : xhr.responseText
resolve(new Response(body, options))
}
xhr.onerror = function() {
reject(new TypeError('Network request failed'))
}
xhr.ontimeout = function() {
reject(new TypeError('Network request failed'))
}
xhr.onabort = function() {
reject(new DOMException('Aborted', 'AbortError'))
}
xhr.open(request.method, request.url, true)
if (request.credentials === 'include') {
xhr.withCredentials = true
} else if (request.credentials === 'omit') {
xhr.withCredentials = false
}
if ('responseType' in xhr && support.blob) {
xhr.responseType = 'blob'
}
request.headers.forEach(function(value, name) {
xhr.setRequestHeader(name, value)
})
if (request.signal) {
request.signal.addEventListener('abort', abortXhr)
xhr.onreadystatechange = function() {
// DONE (success or failure)
if (xhr.readyState === 4) {
request.signal.removeEventListener('abort', abortXhr)
}
}
}
xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit)
})
}
// 封装
if (!self.fetch) {
self.fetch = fetch
self.Headers = Headers
self.Request = Request
self.Response = Response
}
...
三. axios, fetch的区别
fetch是更底层的API,axios是把这些API都做好了封装,使用更方便。我觉得和 axios 相比,fetch的可选择面更广。
Fetch返回的是一个未处理的Promise对象,你可以按需求自由处理。// response.json(); response.text();