使用XHR发送一个json请求一般是这样:
1 const xhr = new XMLHttpRequest() 2 xhr.open('Get', url) 3 xhr.responseType = 'json' 4 5 xhr.onload = () => { 6 console.log(xhr.response) 7 } 8 9 xhr.onerror = () => {10 console.log('error')11 }12 13 xhr.send()
使用fetch的实例:
1 fetch(url).then(response => response.json())2 .then(data => console.log(data))3 .catch(e => console.log('error', e))
Fetch参数
fetch(input [,init])
input(必须) 定义要获取的资源(请求地址)
init(可选)
参数 | 描述
method 请求使用的方法,如GET、POST
headers http请求头(user-Agent, Cookie)
body 请求的body信息
mode
fetch可以设置不同的模式使得请求有效. 模式可在fetch方法的第二个参数对象中定义.
可定义的模式如下:
- same-origin: 表示同域下可请求成功; 反之, 浏览器将拒绝发送本次fetch, 同时抛出错误 “TypeError: Failed to fetch(…)”.
- cors: 表示同域和带有CORS响应头的跨域下可请求成功. 其他请求将被拒绝.
- cors-with-forced-preflight: 表示在发出请求前, 将执行preflight检查.
- no-cors: 常用于跨域请求不带CORS响应头场景, 此时响应类型为 “opaque”.
除此之外, 还有两种不太常用的mode类型, 分别是 navigate , websocket , 它们是 HTML标准 中特殊的值, 这里不做详细介绍.
credentials
omit(缺省值,默认为该值)、same-origin(同源,便是同域请求才发送cookie)、include(任何请求都带cookie)
cache
- default(表示fetch请求之前将检查下http的缓存)
- no-store(表示fetch请求将完全忽略http缓存的存在,这意味着请求之前将不再检查下http的缓存, 拿到响应后, 它也不会更新http缓存)
- no-cache(如果存在缓存,那么fetch将发送一个条件查询request和一个正常的request, 拿到响应后,它会更新http缓存)
- reload(表示fetch请求之前将忽略http缓存的存在, 但是请求拿到响应后,它将主动更新http缓存)
- force-cache(表示fetch请求不顾一切的依赖缓存, 即使缓存过期了,它依然从缓存中读取. 除非没有任何缓存, 那么它将发送一个正常的request)
- only-if-cached(表示fetch请求不顾一切的依赖缓存, 即使缓存过期了,它依然从缓存中读取. 如果没有缓存, 它将抛出网络错误(该设置只在mode为”same-origin”时有效).
如果fetch请求的header里包含 If-Modified-Since, If-None-Match, If-Unmodified-Since, If-Match, 或者 If-Range 之一, 且cache的值为 default , 那么fetch将自动把 cache的值设置为 "no-store"
Fetch - response type
一个fetch请求的相应类型(response.type)为如下三种之一:
- baisc (同域下,相应类型为basic)
- cors (同样是跨域下, 如果服务器返回了CORS响应头, 那么响应类型将为 “cors”. 此时响应头中除 Cache-Control , Content-Language , Content-Type , Expores , Last-Modified 和 Progma 之外的字段都不可见.)
- opaque ( 跨域下, 服务器没有返回CORS响应头, 响应类型为 “opaque”. 此时我们几乎不能查看任何有价值的信息, 比如不能查看response, status, url等等等等.)
注意: 无论是同域还是跨域, 以上 fetch 请求都到达了服务器.
Fetch 常见坑
1.Fetch 请求默认是不带 cookie 的,需要设置 fetch(url, {credentials: 'include'})
默认情况下, fetch 不会从服务端发送或接收任何 cookies, 如果站点依赖于维护一个用户会话,则导致未经认证的请求(要发送 cookies,必须发送凭据头)
2.服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。
当接收到一个代表错误的 HTTP 状态码时,从 fetch()返回的 Promise 不会被标记为 reject, 即使该 HTTP 响应的状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve (但是会将 reolve 的返回值的 ok 属性设置为 false, 想要精确判断fetch()是否成功,需要包含 promise resolved 的情况,此时再判断 Response.ok 是不是为 true。HTTP状态码为200-299是才会设置为true), 仅当网络故障时或请求被阻止时,才会标记为 rejec
使用Fetch封装request方法
http.js
1 import 'whatwg-fetch'; 2 3 const netErrorStatu = 1; // 网络错误 4 const serverErrorStatu = 2; // 服务器错误 5 const formatErrorStatu = 3; // 数据格式错误 6 const logicErrorStatu = 4; // 业务逻辑错误 7 8 const errorMsg = { 9 [netErrorStatu]: '网络错误', 10 [serverErrorStatu]: '服务器错误', 11 [formatErrorStatu]: '数据格式错误', 12 [logicErrorStatu]: '业务逻辑错误' 13 }; 14 15 class CustomFetchError { 16 constructor(errno, data) { 17 this.errno = errno; 18 this.msg = errorMsg[errno]; 19 this.data = data; 20 } 21 } 22 23 export function buildQuery(data) { 24 const toString = Object.prototype.toString; 25 26 const res = Object.entries(data).reduce((pre, [key, value]) => { 27 let newValue; 28 29 if (Array.isArray(value) || toString.call(value) === '[object Object]') { 30 newValue = JSON.stringify(value); 31 } else { 32 newValue = (value === null || value === undefined) ? '' : value; 33 } 34 35 pre.push(`${key}=${encodeURIComponent(newValue)}`); 36 37 return pre; 38 }, []); 39 40 return res.join('&'); 41 } 42 43 export async function request(input, opt) { 44 // 设置请求默认带cookie 45 const init = Object.assign({ 46 credentials: 'include' 47 }, opt); 48 49 let res; 50 // 仅当网络故障时或请求被阻止时,才会标记为 rejec 51 try { 52 res = await fetch(input, init); 53 } catch (e) { 54 throw new CustomFetchError(netErrorStatu, e); 55 } 56 // 仅HTTP状态码为200-299是才会设置为true 57 if (!res.ok) { 58 throw new CustomFetchError(serverErrorStatu, res); 59 } 60 61 let data; 62 // fetch()请求返回的response是Stream对象,调用response.json时由于异步读取流对象所以返回的是一个Promise对象 63 try { 64 data = await res.json(); 65 } catch (e) { 66 throw new CustomFetchError(formatErrorStatu, e); 67 } 68 // 根据和后台的约定设置的错误处理 69 if (!data || data.errno !== 0) { 70 throw new CustomFetchError(logicErrorStatu, data); 71 } 72 73 return data.data; 74 } 75 76 export function get(url, params = {}, opt = {}) { 77 const init = Object.assign({ 78 method: 'GET' 79 }, opt); 80 81 // ajax cache 82 Object.assign(params, { 83 timestamp: new Date().getTime() 84 }); 85 86 const paramsStr = buildQuery(params); 87 88 const urlWithQuery = url + (paramsStr ? `?${paramsStr}` : ''); 89 90 return request(urlWithQuery, init); 91 } 92 93 export function post(url, params = {}, opt = {}) { 94 const headers = { 95 'Content-Type': 'application/x-www-form-urlencoded' 96 }; 97 98 const init = Object.assign({ 99 method: 'POST',100 body: buildQuery(params),101 headers102 }, opt);103 104 return request(url, init);105 }106 107 export default {108 request,109 get,110 post111 };
requset.js
1 import { notification } from 'antd'; 2 import Loading from 'components/Loading'; 3 import { LOGIN_URL } from 'constants/basic'; 4 import * as http from './http'; 5 6 const loading = Loading.newInstance(); 7 8 async function request(method, url, params, opt = {}, httpOpt) { 9 /**10 * needLoading 是否添加loading图片11 * checkAccount 验证未登陆是否跳登陆页面12 * showErrorMsg 是都显示通用错误提示13 */14 const {15 needLoading = true,16 checkAccount = true,17 showErrorMsg = true18 } = opt;19 20 if (needLoading) {21 loading.add();22 }23 24 let res;25 26 try {27 res = await http[method](url, params, httpOpt);28 } catch (e) {29 if (checkAccount && e.errno === 4 && e.data.errno === 10000) {30 location.href = LOGIN_URL;31 }32 33 if (showErrorMsg) {34 notification.error({35 message: '提示信息',36 description: e.errno === 4 ? e.data.msg : e.msg37 });38 }39 40 throw e;41 } finally {42 if (needLoading) {43 loading.remove();44 }45 }46 47 return res;48 }49 50 export function get(...arg) {51 return request('get', ...arg);52 }53 54 export function post(...arg) {55 return request('post', ...arg);56 }
github的代码地址: