目录
  1. 1. 三者简介与各自的优劣
    1. 1.1. Ajax
    2. 1.2. Fetch
    3. 1.3. Axios
  2. 2. 使用
    1. 2.1. Ajax
      1. 2.1.1. 封装
    2. 2.2. Fetch
      1. 2.2.1. 请求参数
      2. 2.2.2. 自定义请求对象
      3. 2.2.3. Response 对象
      4. 2.2.4. 检测请求是否成功
      5. 2.2.5. 上传多个文件例子
    3. 2.3. Axios
Ajax、Fetch、Axios的使用与区别

三者简介与各自的优劣

Ajax

Ajax(Asynchronous JavaScript And XML): 本身并不是一种技术,核心是使用 XMLHttpRequest 对象完成异步请求,可以在不重新刷新页面的情况下与服务器通信,交换数据,或更新页面。

优势(虽说技术老旧,但是大部分优势均是 XHR-level2 升级后的,仅支持 IE10+):

  • 可发送跨域请求(服务端允许的情况下);
  • 支持二进制数据;
  • 支持 formData 对象,发送表单数据;
  • 可获取进度信息;
  • 可设置请求超时时间;

劣势:

  • 不符合关注分离,将输入、输出和用事件来跟踪的状态混杂在一个对象里;
  • 不支持现代 Promise,async/await;
  • 数据类型处理、错误处理等都不易操作;(上述缺点都需要封装才能在真实环境中使用)

Fetch

Fetch: Fetch API 是 JS 提供了一个获取资源的接口(底层接口,等同于 XMLHttpRequest),被设计成更具可扩展性和高效性,Fetch 的核心在于对 HTTP 接口的抽象,包括 RequestResponseHeadersBody等。

优势:

  • 语法简洁,更加语义化
  • 基于 Promise 实现,支持 async/await
  • 脱离了 XHR,提供更多 API(request,response 等)

劣势:

  • 只对网络请求报错,对 404/500 等都是成功请求,并不会 reject,只有网络请求出错才会 reject;
  • 默认不携带 cookie,需添加配置项 fetch(url, {credentials: 'include'})
  • 不支持 abort 终止请求、超时控制,使用 setTimeoutPromise.reject 实现的超时控制并不能阻止请求过程,造成流量的浪费;
  • 无法原生检测请求进度,而 XHR 可以;
  • 浏览器兼容问题,且目前很多特性仍为实验性;
Fetch 浏览器兼容

Axios

Axios: 是一个基于 promise 的第三方 HTTP 库,可以用在浏览器和 node.js 中。浏览器中就是基于 XMLHttpRequest 封装的 http 库;

优势:

  • 支持 Promise
  • 客户端支持防止 CSRF,防御 XSRF
  • 丰富的接口 API(并发请求等)
  • 请求/响应拦截器
  • 转换请求和响应数据(自动转换 JSON 数据等)
  • 取消阻止请求

劣势:

  • 非原生,第三方

综上所述,个人认为在 Fetch 完善且大部分支持前, Axios 仍是目前最好的异步请求方案。


使用

Ajax

简例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.readyState == 4){
if((xhr.status>=200 && xhr.status<300) || xhr.status == 304) {
console.log(JSON.parse(xhr.responseText).name);
}else {
...
}
}
}
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.open('POST', 'http://api.example.com/data.json', true);
xhr.send('username=Vincent&age=18');
xhr.onerror = function() {}

如上例所示,XHR 的劣势之一:不符合关注分离,将输入、输出和用事件来跟踪的状态混杂在一个对象里。

XMLHttpRequest 实例属性与方法:

  • onreadystatechange: 监听请求状态(readyState)改变方法
  • setRequestHeader(key, value): 设置请求头
  • open(method, url, isAsync): 设置请求
  • send(data): 发送请求
  • onload: 请求完成回调
  • onerror: 请求失败回调
  • readyState: 请求状态
    • 0: 请求还未初始化
    • 1: 已建立服务器链接
    • 2: 请求已接受
    • 3: 正在处理请求
    • 4: 请求已完成并且响应已准备好
  • status: 响应代码 200304400

封装

使用 promise,数据处理,设置请求头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 封装
function ajax(options) {
// ES6 解构赋值,并设置默认值
let { method = 'GET', url = '', data = null, async = true } = options
// 返回promise实例
return new Promise(function (resolve, reject) {
let xhr = null
let postData = null
if (XMLHttpRequest) {
xhr = new XMLHttpRequest() //如果XMLHttpRequest存在就新建,IE大于9&&非IE有效
} else {
xhr = new ActiveXObject('Microsoft.XMLHTTP') //用于低版本IE
}
// 处理数据
if (!data) {
let params = [] //定义1个空数组
for (let key in data) {
params.push(key + '=' + data[key]) //将opt里的data存到push里
}
postData = params.join('&') //追加个& params
}
// 判断请求方式,是否添加请求头
if (method.toUpperCase() === 'POST') {
xhr.open('POST', url, async) //开始请求
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8') //设置请求头
xhr.send(postData) //发送POST数据
} else if (method.toUpperCase() === 'GET') {
xhr.open('GET', url + '?' + postData, async) //GET请求
xhr.send(null)
}
// 监听请求状态
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
resolve.call(null, xhr.responseText)
} else if (request.status >= 400) {
reject.call(null, xhr)
}
}
}
})
}
// 使用
ajax({
method: 'POST',
url: 'https://vincef0ng.cn/postdata',
data: { name: 'Vincent', age: 18 },
async: true
})
.then((res) => {
console.log(res)
})
.catch((err) => {
console.log(err)
})

Fetch

简例:上传 JSON 数据(加*为属性默认值)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 上传 JSON 数据
postData('http://example.com/answer', { answer: 42 })
.then((data) => console.log(data)) // 返回JSON 来自`response.json()`
.catch((error) => console.error(error))

function postData(url, data) {
return fetch(url, {
body: JSON.stringify(data), // 指定了 'Content-Type' header
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, same-origin, *omit
headers: {
'content-type': 'application/json'
},
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, cors, *same-origin
redirect: 'follow', // manual, *follow, error
referrer: 'no-referrer' // *client, no-referrer
}).then((response) => response.json()) // 解析成 JSON
}

请求参数

fetch(url, options) 第二参数为请求参数,可选配置:

  • method: 请求使用的方法;
  • headers: 请求头信息;
  • body: 请求的 body 信息:可能是一个 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息;
  • mode: 请求的模式,(cors、 no-cors、same-origin);
  • credentials: 请求发送凭证 cookie 模式。
    • omit: 默认,不发送;
    • same-origin: 同源发送;
    • include: 发送,即使跨域;
  • cache: 请求的缓存模式,有如下几种值:
    • default: 根据浏览器 HTTP 缓存匹配,强缓存协商缓存等直接走缓存;
    • no-store: 直接从服务器获取资源,不查看缓存也不会更新缓存;
    • reload: 直接从服务器获取资源,不查看缓存但更新缓存;
    • no-cache: 命中强缓存仍发送请求,资源没更新,则取缓存资源(即协商缓存),无缓存则发送正常请求;
    • force-cache: 有缓存时强制使用缓存,无缓存时正常发送请求;
    • only-if-cached: 有缓存都使用缓存,无缓存报错;
  • redirect: 重定向模式: follow (自动重定向), error (如果产生重定向将自动终止并且抛出一个错误), 或者 manual (手动处理重定向). 在 Chrome 中,Chrome 47 之前的默认值是 follow,从 Chrome 47 开始是 manual。
  • referrer: 请求来源,可以是 no-referrer、client 或一个同源 URL。默认是 client。
  • referrerPolicy: 指定了 HTTP 头部 referer 字段的值。可能为以下值之一: no-referrer、 no-referrer-when-downgrade、 origin、 origin-when-cross-origin、 unsafe-url 。
  • integrity: 包括请求的 subresource integrity 值 ( 例如: sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=)。

自定义请求对象

除了直接 fetch() 传入 url 和配置外,还可直接接收 Request() 构造函数来创建的请求对象,且 Headers() 构造函数可以创建 headers 对象,但两者均还是实验中功能,仅新浏览器支持

1
2
3
4
5
6
7
8
var myHeaders = new Headers()
myHeaders.append('Content-Type', 'text/plain')

var myInit = { method: 'GET', headers: myHeaders, mode: 'cors', cache: 'default' }

var myRequest = new Request('flowers.jpg', myInit)

fetch(myRequest).then()

Headers() 创建 headers 对象的其它方法;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// append() 方法添加
var myHeaders = new Headers()
myHeaders.append('Content-Type', 'text/plain')

// 字面量方法
var myHeaders = new Headers({
'Content-Type': 'text/plain',
'Content-Length': content.length.toString()
})

// 也支持 has()、set()、get()、delete() 方法
myHeaders.has('Content-Type') // 返回 Boolean
myHeaders.set('Content-Type', 'text/html')
myHeaders.get('Content-Length')
myHeaders.delete('X-Custom-Header')

可设置的 header:

  • Accept-Charset, Accept-Encoding
  • Access-Control-Request-Headers
  • Access-Control-Request-Method
  • Connection
  • Content-Length
  • Cookie, Cookie2
  • Date
  • DNT
  • Expect
  • Host
  • Keep-Alive
  • Origin
  • Referer
  • TE
  • Trailer
  • Transfer-Encoding
  • Upgrade
  • Via
  • Proxy-*
  • Sec-*

当然,浏览器为了安全等原因,还有一些无法设置的 header(详见:whatwg - forbidden header name,whatwg 是一个由各浏览器大佬发起的组织,且一度和 w3c 组织吵架)

Response 对象

常见响应对象属性和方法有:

  • response.status: Number,响应状态码;
  • response.statusText: String,响应状态消息;
  • response.ok: Boolean,状态码在 200-299 内为true
  • response.headers: Object,响应头 headers 对象;
  • response.text(): 读取 response,并以文本形式返回 response;
  • response.json(): 将 response 解析为 JSON 对象形式;
  • response.formData(): 以 FormData 对象的形式返回;
  • response.blob(): 以 Blob(具有类型的二进制数据)形式返回;
  • response.arrayBuffer(): 以 ArrayBuffer(低级别的二进制数据)形式返回;

检测请求是否成功

上述 Fetch 缺点中也提到了,Fetch只对网络请求报错,对 404/500 等都是成功请求,只有网络请求出错才会 reject;检测是否非网络故障报错需要对响应做判断,简单实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fetch('flowers.jpg')
.then(function (response) {
if (response.ok) {
return response.blob()
}
throw new Error('请求失败:' + response.status + response.statusText)
})
.then(function (myBlob) {
var objectURL = URL.createObjectURL(myBlob)
myImage.src = objectURL
})
.catch(function (error) {
console.log('请求网络故障: ', error.message)
})

上传多个文件例子

HTML <input id="files" type="file" multiple> 结合 FormData()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var formData = new FormData()
var photos = document.querySelector('#files')

formData.append('title', 'My photos')
// 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))

Axios

关于 Axios 的使用,可参考我的另一篇博客文章,详细介绍了 Axios API、请求参数、响应对象、实例使用、拦截器、封装 等,详见:Axios HTTP 库的使用 - 前端随笔


参考资料:

文章作者: Vincent F0ng
文章链接: https://vincef0ng.cn/post/ajax-fetch-axios/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Vincent F0ng
请喝奶茶
  • 微信
  • 支付宝
领红包

评论(支持Markdown)