fetch()
是 XMLHttpRequest 的升级版,用于在 JavaScript 脚本里面发出 HTTP 请求。
浏览器原生提供这个对象。本文详细介绍它的用法。
使用Ajax请求数据一般是这样:
// 1.创建 XMLHttpRequest 对象
var xhr = new XMLHttpRequest();
// 2.设置状态监听函数
xhr.onreadystatechange = function () {
// 状态发生变化时,触发回调函数
if (xhr.readyState !== 4) return;
if (xhr.status === 200) {
// 成功:从服务器获取数据,通过responseText拿到响应的文本
console.log(xhr.responseText)
// do what
} else {
// 失败,根据响应码判断失败原因
new Error(xhr.statusText)
}
};
// 3.规定请求的类型、URL 以及是否异步处理请求
xhr.open("GET", url, true);
// 4.将请求发送到服务器
xhr.send();
使用fetch请求数据则是这样:
fetch(url).then(response => response.json())//解析为可读数据
.then(data => console.log(data))//执行结果是 resolve就调用then方法
.catch(err => console.log("Oh, error", err))//执行结果是 reject就调用catch方法
从两者对比来看,fetch代码精简许多,业务逻辑更清晰明了,使得代码易于维护,可读性更高。
总而言之,Fetch 优点主要有:
1. 语法简洁,语义化,业务逻辑更清晰
2. 基于标准 Promise 实现,支持 async/await
等等优点,下文详细介绍~
Axios是对XMLHttpRequest的封装,受到尤雨溪大神推荐使用。
Axios 是一个基于 promise
网络请求库,作用于node.js
和浏览器
中。 它是 isomorphic
[ˌaɪsə'mɔrfɪk]
的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.js http
模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。
导入axios
:
const axios = require('axios');
// 向给定ID的用户发起请求
axios.get('/user?ID=12345')
.then(function (response) {
// 处理成功情况
console.log(response);
})
.catch(function (error) {
// 处理错误情况
console.log(error);
})
.then(function () {
// 总是会执行
});
◾ 上述请求也可以按以下方式完成(可选):
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
})
.then(function () {
// 总是会执行
});
◾ 同时axios
也支持async/await用法:
async function getUser() {
try {
const response = await axios.get('/user?ID=12345');
console.log(response);
} catch (error) {
console.error(error);
}
}
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
(性能优化)
function getUserAccount() {
return axios.get('/user/12345');
}
function getUserPermissions() {
return axios.get('/user/12345/permissions');
}
Promise.all([getUserAccount(), getUserPermissions()])
.then(function (results) {
const acct = results[0];
const perm = results[1];
});
▪ 发起一个post请求:
//
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
});
▪ 在 node.js 用GET请求获取远程图片:
//
axios({
method: 'get',
url: 'http://bit.ly/2mTM3nY',
responseType: 'stream'
})
.then(function (response) {
response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
});
▪ axios的默认请求方式
// 发起一个 GET 请求 (默认请求方式)
axios('/user/12345');
为了方便起见,已经为所有支持的请求方法提供了别名:
注意
在使用别名方法时, url、method、data 这些属性都不必在配置中指定。
一个请求的响应包含以下信息:
{
// `data` 由服务器提供的响应
data: {
},
// `status` 来自服务器响应的 HTTP 状态码
status: 200,
// `statusText` 来自服务器响应的 HTTP 状态信息
statusText: 'OK',
// `headers` 是服务器响应头
// 所有的 header 名称都是小写,而且可以使用方括号语法访问
// 例如: `response.headers['content-type']`
headers: {
},
// `config` 是 `axios` 请求的配置信息
config: {
},
// `request` 是生成此响应的请求
// 在node.js中它是最后一个ClientRequest实例 (in redirects),
// 在浏览器中则是 XMLHttpRequest 实例
request: {
}
}
当使用 then 时,您将接收如下响应:
axios.get('/user/12345')
.then(function (response) {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
});
您可以指定默认配置,它将作用于每个请求。
全局 axios 默认值:
axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
(高级知识)
在请求或响应被 then 或 catch 处理前拦截它们。
◾ 添加请求拦截器
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
◾ 添加响应拦截器
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
如果你稍后需要移除拦截器,可以这样:
const myInterceptor = axios.interceptors.request.use(function () {
/*...*/});
axios.interceptors.request.eject(myInterceptor);
可以给自定义的 axios 实例添加拦截器。
const instance = axios.create();
instance.interceptors.request.use(function () {
/*...*/});
axios.get('/user/12345')
.catch(function (error) {
if (error.response) {
// 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
// 请求已经成功发起,但没有收到响应
// `error.request` 在浏览器中是 XMLHttpRequest 的实例,
// 而在node.js中是 http.ClientRequest 的实例
console.log(error.request);
} else {
// 发送请求时出了点问题
console.log('Error', error.message);
}
console.log(error.config);
});
不知道你有没有留意到,现在很多主流的网站已经大量开始使用Fetch进行网络请求:
fetch()
的功能与 XMLHttpRequest 基本相同,但有三个主要的差异。
(1)fetch()
使用 Promise,不使用回调函数,因此大大简化了写法,写起来更简洁。
(2)fetch()
采用模块化设计,API 分散在多个对象上(Response
对象、Request
对象、Headers
对象),更合理一些;相比之下,XMLHttpRequest 的 API 设计并不是很好,输入、输出、状态都在同一个接口管理,容易写出非常混乱的代码。
(3)fetch()
通过数据流(Stream 对象)处理数据,可以分块读取,有利于提高网站性能表现,减少内存占用,对于请求大文件或者网速慢的场景相当有用。XMLHTTPRequest 对象不支持数据流,所有的数据必须放在缓存里,不支持分块读取,必须等待全部拿到后,再一次性吐出来。
在用法上,fetch()
接受一个 URL 字符串作为参数,默认向该网址发出 GET 请求,返回一个 Promise 对象。
◾ 它的基本用法如下:
fetch(url).then(response => response.json())//解析为可读数据
.then(data => console.log(data))//执行结果是 resolve就调用then方法
.catch(err => console.log("Oh, error", err))//执行结果是 reject就调用catch方法
上面示例中,fetch()
接收到的response
是一个 Stream 对象,response.json()
是一个异步操作,取出所有内容,并将其转为 JSON 对象。
◾ 下面是一个例子,从服务器获取 JSON 数据。
// 请求阮一峰大佬的GitHub
fetch('https://api.github.com/users/ruanyf')
.then(response => response.json())
.then(json => console.log(json))
.catch(err => console.log('Request Failed', err));
现代浏览器原生支持fetch,所以我们可以直接在浏览器上运行上面的代码:
Promise 可以使用 await 语法改写,使得语义更清晰。
async function getJSON() {
let url = 'https://api.github.com/users/ruanyf';
try {
let response = await fetch(url);
return await response.json();
} catch (error) {
console.log('Request Failed', error);
}
}
上面示例中,await
语句必须放在try...catch
里面,这样才能捕捉异步操作中可能发生的错误。
注意 ❗ ❗ ❗
后文都采用await
的写法,不使用.then()
的写法。
Response
的 json()
方法接收一个 Response
流,并将其读取完成。它返回一个 Promise,Promise 的解析 resolve 结果是将文本体解析为 JSON。
response.json().then(data => {
// do something with your data
});
Response.json()
返回一个被解析为JSON格式的promise
对象,这可以是任何可以由JSON表示的东西 - 一个object
,一个array
,一个string
,一个number
…
fetch()
请求成功以后,得到的是一个 Response 对象。它对应服务器的 HTTP 回应。
const response = await fetch(url);
Response 包含的数据通过 Stream 接口异步读取,但是它还包含一些同步属性,对应 HTTP 回应的标头信息(Headers),可以立即读取。
async function fetchText() {
let response = await fetch('/readme.txt');
console.log(response.status);
console.log(response.statusText);
}
response.status
和response.statusText
就是 Response 的同步属性,可以立即读取。
标头信息属性有下面这些:
▪ Response.ok
Response.ok
属性返回一个布尔值,表示请求是否成功,表明响应是否成功(状态码在200-299范围内)。
▪ Response.status
Response.status
属性返回响应的状态代码(例如,成功为200)。
▪ Response.statusText
Response.statusText
属性返回与状态代码相对应的状态消息(例如请求成功以后,服务器返回"OK")。
▪ Response.url
Response.url
属性返回请求的 URL。如果 URL 存在跳转,该属性返回的是最终 URL。
▪ Response.type
Response.type
属性返回请求的类型。可能的值如下:
fetch()
发出请求以后,有一个很重要的注意点:只有网络错误,或者无法连接时,fetch()
才会报错,其他情况都不会报错,而是认为请求成功。
这就是说,即使服务器返回的状态码是 4xx 或 5xx,fetch()
也不会报错(即 Promise 不会变为 rejected
状态)。
只有通过Response.status
属性,得到 HTTP 回应的真实状态码,才能判断请求是否成功。请看下面的例子。
async function fetchText() {
let response = await fetch('/readme.txt');
if (response.status >= 200 && response.status < 300) {
return await response.text();
} else {
throw new Error(response.statusText);
}
}
上面示例中,response.status
属性只有等于 2xx (200~299),才能认定请求成功。这里不用考虑网址跳转(状态码为 3xx),因为fetch()会将跳转的状态码自动转为 200。
另一种方法是判断response.ok是否为true。
if (response.ok) {
// 请求成功
} else {
// 请求失败
}
Response对象根据服务器返回的不同类型的数据,提供了不同的读取方法。
response.text()
:得到文本字符串,用于获取文本数据,比如 HTML 文件。response.json()
:得到 JSON 对象。前文已经详细讲过了
response.blob()
:得到二进制 Blob 对象。response.formData()
:得到 FormData 表单对象。response.arrayBuffer()
:得到二进制 ArrayBuffer 对象,主要用于获取流媒体文件。上面5个读取方法都是异步的,返回的都是 Promise 对象。必须等到异步操作结束,才能得到服务器返回的完整数据。
fetch()的第一个参数是 URL,还可以接受第二个参数,作为配置对象,定制发出的 HTTP 请求。
fetch(url, optionObj)
上面命令的optionObj就是第二个参数。
HTTP 请求的方法
、标头
、数据体
都在这个对象里面设置。下面是一些示例。
(1)POST 请求
const response = await fetch(url, {
method: 'POST',
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8",
},
body: 'foo=bar&lorem=ipsum',
});
const json = await response.json();
上面示例中,配置对象用到了三个属性。
注意,有些标头不能通过headers属性设置,比如Content-Length、Cookie、Host等等。它们是由浏览器自动生成,无法修改。
(2)提交 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)
});
上面示例中,标头Content-Type要设成'application/json;charset=utf-8'
。因为默认发送的是纯文本,Content-Type的默认值是’text/plain;charset=UTF-8'
。
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
});
fetch()
请求的底层用的是 Request()
对象的接口,参数完全一样,因此上面的 API 也是Request()
的 API。
虽然无法进行直观的比较,但是我们可以从npm包下载量来看:
因为Node环境下默认是不支持Fetch的,所以必须要使用node-fetch
这个包,而这个包的周下载量一路攀升,可以看到已经来到了每周2千六百多万的下载量。这还仅仅是Node环境下,浏览器则是默认支持不需要第三方包。
上面是Axios的下载量,可以看到也是一路攀升,Axios封装的各种方法确实非常的好用。
Axios可以兼容IE浏览器,而Fetch在IE浏览器和一些老版本浏览器上没有受到支持,但是有一个库可以让老版本浏览器支持Fetch即它就是whatwg-fetch
,它可以让你在老版本的浏览器中也可以使用Fetch,并且现在很多网站的开发都为了减少成本而选择不再兼容IE浏览器。
来看一下如何使用Axios和Fetch进行请求。
Axios:
const options = {
url: "http://example.com/",
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
},
data: {
a: 10,
b: 20,
},
};
axios(options).then((response) => {
console.log(response.status);
});
Fetch:
const url = "http://example.com/";
const options = {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json;charset=UTF-8",
},
body: JSON.stringify({
a: 10,
b: 20,
}),
};
fetch(url, options).then((response) => {
console.log(response.status);
});
其中最大的不同之处在于传递数据的方式不同,Axios是放到data属性里,以对象的方式进行传递,而Fetch则是需要放在body属性中,以字符串的方式进行传递。
Axios的相应超时设置是非常简单的,直接设置timeout
属性就可以了,而Fetch设置起来就远比Axios麻烦,这也是很多人更喜欢Axios而不太喜欢Fetch的原因之一。
axios({
method: "post",
url: "http://example.com/",
timeout: 4000, // 请求4秒无响应则会超时
data: {
firstName: "David",
lastName: "Pollock",
},
})
.then((response) => {
/* 处理响应 */
})
.catch((error) => console.error("请求超时"));
Fetch提供了AbortController
属性,但是使用起来不像Axios那么简单。
const controller = new AbortController();
const options = {
method: "POST",
signal: controller.signal,
body: JSON.stringify({
firstName: "David",
lastName: "Pollock",
}),
};
const promise = fetch("http://example.com/", options);
// 如果4秒钟没有响应则超时
const timeoutId = setTimeout(() => controller.abort(), 4000);
promise
.then((response) => {
/* 处理响应 */
})
.catch((error) => console.error("请求超时"));
Axios还有非常好的一点就是会自动对数据进行转换,而Fetch则不同,它需要开发者手动转化。
// axios
axios.get("http://example.com/").then(
(response) => {
console.log(response.data);
},
(error) => {
console.log(error);
}
);
// fetch
fetch("http://example.com/")
.then((response) => response.json()) // 使用response.json()方法 将返回的文本数据解析为JSON数据 以供使用
.then((data) => {
console.log(data);
})
.catch((error) => console.error(error));
使用Fetch时你需要清楚请求后的数据类型是什么,然后再用对应的方法将它进行转换。
Axios的一大卖点就是它提供了拦截器,可以统一对请求或响应进行一些处理,相信如果看过一个比较完整的项目的请求封装的话,一定对Axios的拦截器有一定的了解,它是一个非常重要的功能。
使用它可以为请求附加token、为请求增加时间戳防止请求缓存,以及拦截响应,一旦状态码不符合预期则直接将响应消息通过弹框的形式展示在界面上,比如密码错误、服务器内部错误、表单验证不通过等问题。
下面章节详细讲解Axios拦截器的配置与使用!
而Fetch没有拦截器功能,但是要实现该功能并不难,直接重写全局Fetch方法就可以办到。
在项目中大量数据反显会用到 多个并发请求
:
Axios:
axios
.all([
axios.get("https://api.github.com/users/iliakan"),
axios.get("https://api.github.com/users/taylorotwell"),
])
.then(
axios.spread((obj1, obj2) => {
...
})
);
Fetch:
Promise.all([
fetch("https://api.github.com/users/iliakan"),
fetch("https://api.github.com/users/taylorotwell"),
])
.then(async ([res1, res2]) => {
const a = await res1.json();
const b = await res2.json();
})
.catch((error) => {
console.log(error);
});
Fetch碾压Axios的一点就是现代浏览器的原生支持。
不像Axios需要引入一个包,而且需要即时测试某些接口直接在Chrome浏览器中使用Fetch进行请求,尤其是编写爬虫或脚本的时候,你在当前网页打开Chrome的控制台使用Fetch几乎不需要什么配置就可以直接进行请求。
总的来说,现阶段来看 Axios比Fetch好用,Axios使用体验确实优于Fetch。
那么为什么目前在很多大公司的网站上面都开始使用Fetch进行网络请求呢?
结论:在浏览器原生支持上,Fetch优势明显。
前文讲到过axios有很多优点和特性,正是因为axios的强大,所以vue官方开发组放弃了对其官方库vue-resource
的维护,直接推荐我们使用axios
库。
在很多情况我们都要对发送的http请求和其响应进行特定的处理;例如每个请求都附带后端返回的token
,拿到response
之前loading
动画的展示等。如果请求数非常多,这样处理起来会非常的麻烦,程序的优雅性也会大打折扣。
在这种情况下,axios
为开发者提供了这样一个API:拦截器。
拦截器分为 请求(request)拦截器 和 响应(response)拦截器。
1.axios的基础配置
项目目录如下图所示:
其中,api
目录下一般存放的是页面的请求,这些请求都需要统一经过请求拦截器的处理,这部分不是重点。
比如:
重点在于request文件
的编写,以下是axios进行基础配置的部分代码
// 在http.js中引入axios
import axios from 'axios' //引入 axios
import QS from 'qs'; // 引入qs模块,用来序列化post类型的数据,某些请求会用得到
import {
Message } from 'element-ui' //引入 element-ui 的 Message 模块,用于信息提示
import store from '@/store' //引入 vuex 中的数据
import {
getToken } from '@/utils/auth' //引入拿到的权限tocken
// create an axios instance 创建axios实例
const service = axios.create({
baseURL: process.env.BASE_API, // api 的 base_url
timeout: 5000, // request timeout 设置请求超时时间
responseType: "json",
withCredentials: true, // 是否允许带cookie这些
headers: {
"Content-Type": "application/json;charset=utf-8"
}
})
请求拦截器的作用是在请求发送前进行一些操作,例如在每个请求体里加上token,统一做了处理如果以后要改也非常容易。
话不多说,直接上代码:
// create an axios instance
service.interceptors.request.use(
config => {
// 在发送请求之前做什么
if (config.method === "post") {
// 序列化
// config.data = qs.stringify(config.data);
// config.data = JSON.stringify(config.data);
// 温馨提示,若是贵公司的提交能直接接受json 格式,可以不用 qs 来序列化的
}else {
if (store.getters.token) {
// 若是有做鉴权token , 就给头部带上token
// 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改
// 若是需要跨站点,存放到 cookie 会好一点,限制也没那么多,有些浏览环境限制了 localstorage (隐身模式)的使用
config.headers['X-Token'] = getToken()
}
}
return config;
},
error => {
// 对请求错误做些什么,自己定义
Message({
//使用element-ui的message进行信息提示
showClose: true,
message: error,
type: "warning"
});
return Promise.reject(error);
}
这里说一下token
,一般是在登录完成之后,将用户的token
通过localStorage
或者cookie
存在本地,然后用户每次在进入页面的时候,会首先从本地存储中读取token
,如果token
存在说明用户已经登陆过,则更新vuex
中的token
状态。然后,在每次请求接口的时候,都会在请求的header中携带token
,服务器就可以根据你携带的token
来判断你的登录是否过期,如果没有携带,则说明没有登录过。
响应拦截器的作用是在接收到响应后进行一些操作,例如在服务器返回登录状态失效,需要重新登录的时候,跳转到登录页等。
话不多说,直接上代码:
// response interceptor
service.interceptors.response.use(
response => {
// 如果返回的状态码为200,说明接口请求成功,可以正常拿到数据
// 否则的话抛出错误
if (response.status === 200) {
return Promise.resolve(response);
} else {
return Promise.reject(response);
}
},
// 服务器状态码不是2开头的的情况
// 这里可以跟你们的后台开发人员协商好统一的错误状态码
// 然后根据返回的状态码进行一些操作,例如登录过期提示,错误提示等等
// 下面列举几个常见的操作,其他需求可自行扩展
error => {
if (error.response.status) {
switch (error.response.status) {
// 401: 未登录
// 未登录则跳转登录页面,并携带当前页面的路径
// 在登录成功后返回当前页面,这一步需要在登录页操作。
case 401:
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
});
break;
// 403 token过期
// 登录过期对用户进行提示
// 清除本地token和清空vuex中token对象
// 跳转登录页面
case 403:
Message({
message: '登录过期,请重新登录',
duration: 1000,
forbidClick: true
});
// 清除token
localStorage.removeItem('token');
store.commit('loginSuccess', null);
// 跳转登录页面,并将要浏览的页面fullPath传过去,登录成功后跳转需要访问的页面
setTimeout(() => {
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
});
}, 1000);
break;
// 404请求不存在
case 404:
Message({
message: '网络请求不存在',
duration: 1500,
forbidClick: true
});
break;
// 其他错误,直接抛出错误提示
default:
Message({
message: error.response.data.message,
duration: 1500,
forbidClick: true
});
}
return Promise.reject(error.response);
}
}
});
响应拦截器很好理解,就是服务器返回给我们的数据,我们在拿到之前可以对他进行一些处理。例如:如果后台返回的状态码是200,则正常返回数据,否则的根据错误的状态码类型进行一些我们需要的错误,其实这里主要就是进行了错误的统一处理和没登录或登录过期后调整登录页的一个操作。
axios封装好之后,调用就很简单了。我们把接口统一写在api文件夹中。(如果你的业务非常复杂,建议把不同模块或组件的请求分开写到不同的文件里,这样方便维护)。
// api.js
import request from '@/utils/request'
export function userSearch(name) {
return request({
url: '/search/user',
method: 'get',
params: {
name }
})
}
然后在具体的组件中进行调用即可
import {
userSearch} from '@/api/api'
export default {
data() {
return {
name: '大大大大大西瓜G'
}
},
methods:{
getUserInfo () {
userSearch(this.name).then(res => {
//对拿到的res.data进行一番操作或者渲染
})
}
},
mounted() {
this.getUserInfo ();
}
}