Open lulujianglab opened 5 years ago
在前端这个迅猛发展的领域,前端请求方法也是迭出不穷,从原生 XHR 到 jQuery ajax,再到现在的 axios 和 fetch,那发请求的最佳实践是什么呢?这个答案我们在文章中会慢慢给出
XHR
jQuery ajax
axios
fetch
在分析 axios 封装方法之前,我们先来看看 jQuery ajax 和 fetch 方法
jQuery ajax 是对原生 XHR 的封装,还支持 JSONP
JSONP
$.ajax({ type: 'POST', url: url, data: data, dataType: dataType, success: function() {}, error: function() {} })
但是随着 react,vue 等前端框架的兴起,jQuery 早已不复当年之勇
react
vue
jQuery
且 jQuery 整个项目太大,单纯使用 ajax 却要引入整个 jQuery 非常的不合理,于是便有了 fetch 的解决方案
ajax
fetch 号称是 ajax 的替代品,它的 API 是基于 Promise 设计的
API
Promise
而且 fetch 也是 whatwg 的标准,具体可以参考 fetch Living Standard
// 原生 XHR var xhr = new XMLHttpRequest() xhr.open('GET', url) xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { console.log(xhr.responseText) // 从服务器获取数据 } } xhr.send() // fetch fetch(url).then(response => { if (response.ok) { response.json() } }) .then(data => console.log(data)) .catch(err => console.log(err))
再搭配上 async / await 将会让我们的异步代码更加优雅
async
await
async function test() { let response = await fetch(url) let data = await response.json() console.log(data) }
fetch 相对于是更加底层的,很多情况下都需要我们再次封装,比如需要手动将参数拼接成 'name=test' 的格式,而 jquery ajax 已经封装好了
jquery ajax
// jquery ajax $.post(url, {name: 'test'}) // fetch fetch(url, { method: 'POST', body: Object.keys({name: 'test'}).map((key) => { return `${encodeURIComponent(key)} = ${encodeURIComponent(params[key])}` }).join('&') })
除此之外,fetch 还有很多问题,比如
fetch 只对网络请求报错,对 400,500 都当做成功的请求,需要封装去处理
fetch 不支持取消(最大的问题),不支持超时控制,使用 setTimeout 及 Promise.reject 实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费
setTimeout
Promise.reject
fetch 没有办法原生监测请求的进度,而 XHR 可以
具体问题可以参考 fetch 没有你想象的那么美 和 fetch 使用的常见问题及解决方法
所以 fetch 并不是开箱即用的
对比于 jQuery ajax 、 fetch 、 axios 的优点简直不要太多,它也是 vue 官网推荐使用的
axios 是一个基于 Promise 的 HTTP 库,可以用于浏览器和 nodejs 中,主要特性有
HTTP
nodejs
XMLHttpRequest
node.js
http
Promise API
JSON
XSRF
axios 本质上也是对原生 XHR 的封装
axios.post('/upload', { userName: 'zhangsan' }) .then(res => { console.log(res, res.data) }) .catch(err => { console.log(err) })
对于客户端支持防御 XSRF,实际上就是让你的每个请求都带一个从 cookie 中拿到的 key,根据浏览器同源策略,假冒的网站是拿不到你 cookie 中的 key,这样后台就可以轻松辨别出这个请求是否是用户在假冒网站上的误导输入,从而采取正确的策略
cookie
key
axios 既提供了并发的封装方法,也没有 fetch 的各种问题,而且体积也较小,当之无愧现在最应该选用的请求的方式
axios 支持同构, 对于既要支持 SPA 同时支持 SSR 的项目来说非常好用
具体的使用方法我们可以查看 axios 中文文档
下面是一些封装方法的详细使用
let q1 = axios.post('/add', 'b=2') let q2 = axios.post('/upload', 'a=1') // 合并这两个请求,并处理其成功和失败 // 一般会用于两个分开的相关联的请求,缺一不可 axios.all([q1, q2]) .then(axios.spread((res1, res2) => { // 全部成功 console.log(res1, res2) })) .catch(err => { // 其一失败 console.log(err) })
// axios.headers = {} // 覆盖原本默认头 // axios.defalut.headers.accept = 'abc' // 走默认头,修改个例 // 请求一 axios.get('/', { params: {id: 1}, // transformResponse 在传递给 then/catch 前,允许修改响应数据 transformResponse: (data) => { console.log(data) // 就是 res.data,可以更改 data = 'data changed' return data } }) .then(res => { console.log(res, res.data) }) .catch(err => { console.log(err) }) // 请求二 axios.post('/upload', 'name=jack', { timeout: 1000, // transformRequest 允许在向服务器发送前,修改请求数据 // 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法 transformRequest: (data) => { // 加工请求体数据 return 'name = rose' } }) .then(res => { console.log(res, res.data) }) .catch(err => { console.log(err) })
这里我们以上传文件为例,在 vuejs 中使用
choose file : <input type = "file" name = "file" @change = "changeFile" /> <button @click = "sendAjax">sendReq</button> <button @click = "cancelAjax">cancelReq</button> <button @click = "resumeAjax">ContinueSendlReq</button>
// 发送请求 sendAjax() { // 在请求前需要先生成独立的标识,才能更方便取消请求 const CancelToken = axios.CancelToken // 获取取消标识 const source = CancelToken.source() // 使用 CancelToken.source 工厂方法创建 cancel token // 保存起来方便取消调用 this.source = source let fd = new FormData() fd.append('file', this.file) axios.post('/uploadfile', fd, { // 携带取消标识 cancelToken: source.token, onUploadProgress: (progressEvent) => { console.log(progressEvent.loaded) console.log(progressEvent.total) // 保存最后上传大小 this.loaded = progressEvent.loaded this.rate = (progressEvent.loaded / progressEvent.total) * 100 } }) .then(res => { console.log(res.data) }) .catch(err => { console.log(err) }) }, // 取消请求 cancelAjax() { this.source.cancel() // 通过内部存好的方法做取消 }, // 继续请求 resumeAjax() { // 剪裁文件 开始 结尾 const fileData = this.file.slice(this.loaded + 1, this.file.size) let fd = new FormData() fd.append('file', fileData) // 为了后续续传以后,再取消 const CancelToken = axios.CancelToken // 获取取消标识 const source = CancelToken.source() // 使用 CancelToken.source 工厂方法创建 cancel token this.source = source axios.post('/uploadfile', fd, { // 携带取消标识 cancelToken: source.token, onUploadProgress: (progressEvent) => { // 保存最后上传大小 this.loaded = progressEvent.loaded this.rate = (progressEvent.loaded / progressEvent.total) * 100 } }) .then(res => { console.log(res.data) }) .catch(err => { console.log(err) }) }, // 上传文件 changeFile(e) { // console.log(e.target.files[0]) this.file = e.target.files[0] }
为了更方便的取消请求,我们在发送请求时,需要有独立的标识
取消请求大概来说分为三步
CancelToken.source
cancel
token
在请求或响应被 then 或 catch 处理前拦截它们
then
catch
比如实现一个功能如下
可以通过设置一个标识表示 loading 出现与否,默认是不出现,在在发送请求之前将 loading 标识设为 true,拿到响应数据之后,将 loading 标识设为 false
data() { return { isshow: false // loading默认不出现 } },
添加请求拦截器
// 配置请求拦截器 use 给请求之前做的事可以是多件,可以 use 多次 axios.interceptors.request.use((config) => { // 在发送请求之前做些什么 console.log(config) this.isshow = true // loading 出现 return config }, (error) => { return Promise.reject(error) })
添加响应拦截器
axios.interceptors.response.use((res) => { // 对响应数据做点什么 console.log(res) // res.config this.isshow = false // loading 消失 return res }, (error) => { return Promise.reject(error) })
在拦截器中我们也可以实现一个类似 cookie 的机制,比如现在广泛使用的 jwt,通过客户端保存数据,再在请求中带回服务器
在服务端代码中设置响应头中包含 token: 'abc' 的信息
token: 'abc'
server.post('/token', function (req, res){ console.log('body', req.body) // 给客户端一个标识 res.set('token', 'abc') res.send({token: 'abc', msg: 'post请求成功'}) })
然后前端请求数据,通过响应拦截器将 token 存在本地,使用 localStorage
localStorage
axios.interceptors.response.use((res) => { // 获取服务器的响应头 if(res.headers.token) { var token = res.headers.token localStorage.setItem('token', token) } return res }, (error) => { return Promise.reject(error) })
然后再次发送请求时,通过请求拦截器从本地取出 token 放入请求头中
axios.interceptors.request.use((config) => { // 设置请求头,类似cookie var token = localStorage.getItem('token') if(token) { config.headers['token'] = token } return config }, (error) => { return Promise.reject(error) })
在 node 中,我们可能遇到需要向另一个服务器 post 数据的需求,这个时候如果使用 axios 会非常方便,毕竟有原生支持的 Promise 语法
const querystring = require('querystring') axios.post('http://xxx.com/', querystring.stringify({ userName: 'zhangsan' }))
其他的方法使用这里就不做过多赘述了,具体的使用可以查看 axios 中文文档
所以,发请求的最佳实践是什么呢?综上,当之无愧现在最应该选用的请求的方式就是 axios
上述用到的所有代码完整版可以查看 axios-demo
在前端这个迅猛发展的领域,前端请求方法也是迭出不穷,从原生
XHR
到jQuery ajax
,再到现在的axios
和fetch
,那发请求的最佳实践是什么呢?这个答案我们在文章中会慢慢给出在分析
axios
封装方法之前,我们先来看看jQuery ajax
和fetch
方法jQuery ajax
jQuery ajax
是对原生XHR
的封装,还支持JSONP
但是随着
react
,vue
等前端框架的兴起,jQuery
早已不复当年之勇且
jQuery
整个项目太大,单纯使用ajax
却要引入整个jQuery
非常的不合理,于是便有了fetch
的解决方案fetch
fetch
号称是ajax
的替代品,它的API
是基于Promise
设计的而且
fetch
也是 whatwg 的标准,具体可以参考 fetch Living Standard再搭配上
async
/await
将会让我们的异步代码更加优雅fetch
相对于是更加底层的,很多情况下都需要我们再次封装,比如需要手动将参数拼接成 'name=test' 的格式,而jquery ajax
已经封装好了除此之外,
fetch
还有很多问题,比如fetch
只对网络请求报错,对 400,500 都当做成功的请求,需要封装去处理fetch
不支持取消(最大的问题),不支持超时控制,使用setTimeout
及Promise.reject
实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费fetch
没有办法原生监测请求的进度,而XHR
可以具体问题可以参考 fetch 没有你想象的那么美 和 fetch 使用的常见问题及解决方法
所以 fetch 并不是开箱即用的
axios
对比于
jQuery ajax
、fetch
、axios
的优点简直不要太多,它也是vue
官网推荐使用的axios
是一个基于Promise
的HTTP
库,可以用于浏览器和nodejs
中,主要特性有XMLHttpRequest
node.js
创建http
请求Promise API
JSON
数据XSRF
axios
本质上也是对原生XHR
的封装对于客户端支持防御 XSRF,实际上就是让你的每个请求都带一个从
cookie
中拿到的key
,根据浏览器同源策略,假冒的网站是拿不到你cookie
中的key
,这样后台就可以轻松辨别出这个请求是否是用户在假冒网站上的误导输入,从而采取正确的策略axios
既提供了并发的封装方法,也没有fetch
的各种问题,而且体积也较小,当之无愧现在最应该选用的请求的方式axios 支持同构, 对于既要支持 SPA 同时支持 SSR 的项目来说非常好用
具体的使用方法我们可以查看 axios 中文文档
下面是一些封装方法的详细使用
合并请求
options 参数
取消请求
这里我们以上传文件为例,在 vuejs 中使用
为了更方便的取消请求,我们在发送请求时,需要有独立的标识
取消请求大概来说分为三步
CancelToken.source
工厂方法创建cancel
token
,保存起来方便取消调用拦截器
在请求或响应被
then
或catch
处理前拦截它们比如实现一个功能如下
可以通过设置一个标识表示 loading 出现与否,默认是不出现,在在发送请求之前将 loading 标识设为 true,拿到响应数据之后,将 loading 标识设为 false
添加请求拦截器
添加响应拦截器
在拦截器中我们也可以实现一个类似 cookie 的机制,比如现在广泛使用的 jwt,通过客户端保存数据,再在请求中带回服务器
在服务端代码中设置响应头中包含
token: 'abc'
的信息然后前端请求数据,通过响应拦截器将 token 存在本地,使用
localStorage
然后再次发送请求时,通过请求拦截器从本地取出 token 放入请求头中
nodejs 使用
在 node 中,我们可能遇到需要向另一个服务器 post 数据的需求,这个时候如果使用 axios 会非常方便,毕竟有原生支持的 Promise 语法
其他的方法使用这里就不做过多赘述了,具体的使用可以查看 axios 中文文档
所以,发请求的最佳实践是什么呢?综上,当之无愧现在最应该选用的请求的方式就是 axios
上述用到的所有代码完整版可以查看 axios-demo