React-Fetch
引言:
fetch在React-js中相当于XMLHttpRequest,它提供了许多与XMLHttpRequest相同的功能,但被设计成更具可拓展性和高效性。XMLHttpRequest在发送web请求时需要开发者配置相关请求信息和成功后的回调,尽管开发者只关心成功后的业务处理,但是也要配置繁琐的内容,导致配置和调用比较混乱,也不符合关注分离的原则,fetch的出现正是为了解决XMLHttpRequest出现的这些问题。
使用fetch的理由参见传统Ajax已死,Fetch永生
fetch是基于promise设计的,有三大优点:
1. 语法简单,更加语义化
2. 基于标准的promise实现,支持async/await
3. 使用ismorphic-fetch,方便同构
常见问题:
fetch兼容性
fetch在各个浏览器低版本的情况下是不被支持的,由于fetch是基于promise实现的,所以在低版本浏览器中promise未必被原生支持。所以,二者都需要polyfill来解决问题:
promise:es6-promise,babel-promise
fetch:whatwg-fetch,ismorphic-fetch(同构)
fetch默认不携带cookie
fetch不管在同域还是在跨域的情况下,默认都不携带cookie的,所以那些需要权限验证的请求就无法正常获取到数据,这时候需要配置credentials项,有一下三个选项可添:
omit: 默认值,忽略cookie的发送
same-origin: 表示cookie只能同域发送,不能跨域发送
include: 表示既可以同域发送,也可以跨域发送
fetch请求对某些错误的http状态不会reject
这主要是fetch返回promise导致的,因为fetch返回的promise在某些错误的http状态下如400、500不会reject,相反它会被resolve(但是resolve的ok返回值为false),所以一般会对fetch请求做一层封装:
function checkStatus(response) {
if (response.status >= 200 && response.status < 300) {
return response;
}
const error = new Error(response.statusText);
error.response = response;
throw error;
}
function parseJSON(response) {
return response.json();
}
export default function request(url, options) {
let opt = options||{};
return fetch(url, {credentials: 'include', ...opt})
.then(checkStatus)
.then(parseJSON)
.then((data) => ( data ))
.catch((err) => ( err ));
}
fetch跨域问题
可以对fetch的mode项进行配置,有一下三个选项:
same-origin:该模式是不允许跨域的,它需要遵循同源策略,否则浏览器会返回一个error告知
不能跨域,其对应的response type 为basic
cors: 该模式支持跨域请求,顾名思义,它是以cors形式跨域,当然该模式也可以在同域请求
下不需要后端额外的cors支持,其对应的response type 为cors
no-cors: 该模式用于跨域请求但是服务器不带cors响应头,也就是服务器不支持cors,这也
是fetch特殊的请求跨域方式,其对应的response type 为opaque
针对跨域请求,cors跨域是常见的跨域请求实现。
fetch实现步骤
1. 导入
install : npm install whatwg-fetch
2. fetch获取数据
fetch('https://api.github.com/users/chriscoyier/repos')
.then(response => {/* do something */})
fetch是基于promise的,所以在response的箭头函数内部可以做自己想做的事情。
有三种方式解析获取到的数据:
1 json数据 用reponse.json()来解析
2 xml格式文件 用response.text()来解析
3 图片文件 用response.blob()来解析
返回的response通常有一下几部分组成:
{
body: ReadableStream
bodyUsed: false
headers: Headers
ok : true
redirected : false
status : 200 statusText : "OK" type : "cors" url : "http://some-website.com/some-url" __proto__ : Response }
response每次响应后,只有ok,status,statusText是不同的,以上status:200,statusText:'OK',证明获取成功。
3. 发送数据
使用fetch发送数据,只需要配置三个参数:
fetch('some-url', option)
第一个参数是设置请求方法,如post、get或者del,fetch会自动设置为get
第二个参数是设计头部,一般使用json数据格式,所以一般设置contentType为application/json
第三个参数是json内容的主体,因为json内容是必须的,所以调用时会使用JSON.stringify
实践中post请求会像下面这样
let content = {some: 'content'};
// The actual fetch request
fetch('some-url', {
method: 'post',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(content)
})
// .then()...
fetch异常处理
虽然ajax响应成功,但是仍然会有问题出现:
1 可能获取不存在的资源
2 没有权限获取资源
3 输入参数有误
4 服务器抛出异常
5 服务器超时
6 服务器崩溃
7 API更改
8 ......
最普遍的方法就是catch
fetch('https://api.github.com/users/chrissycoyier/repos')
.then(response => response.json())
.then(data => console.log('data is', data))
.catch(error => console.log('error is', error));
详情参见使用fetch
处理其他响应类型
fetch('some-url')
.then(handleResponse)
.then(data => console.log(data))
.then(error => console.log(error))
function handleResponse (response) {
let contentType = response.headers.get('content-type')
if (contentType.includes('application/json')) {
return handleJSONResponse(response)
} else if (contentType.includes('text/html')) {
return handleTextResponse(response)
} else {
// Other response types as necessary. I haven't found a need for them yet though.
throw new Error(`Sorry, content-type ${contentType} not supported`)
}
}
function handleJSONResponse (response) {
return response.json()
.then(json => {
if (response.ok) {
return json
} else {
return Promise.reject(Object.assign({}, json, {
status: response.status,
statusText: response.statusText
}))
}
})
}
function handleTextResponse (response) {
return response.text()
.then(text => {
if (response.ok) {
return json
} else {
return Promise.reject({
status: response.status,
statusText: response.statusText,
err: text
})
}
})
}