Ajax是Javascript的核心,在SPA(单页应用)中也获得广泛的使用。它是我们页面和服务端之间通讯的桥梁。虽然目前也有了新的fetch,但是实际上Ajax还是暂时没有离开我们的视线(还有很多面试官喜欢问)。所以在这篇文章中,我会通过下面几个section来说说当今前端Ajax的使用。
怎样创建一个Ajax调用
1 | const xhr = new XMLHttpRequest() // IE已经退出历史帷幕,这里不再考虑ActiveXObject兼容 |
上面三行代码就实现了一个发送给服务端的Ajax请求了。
1) 第一行创建了一个XMLHttpRequest
的实体类,通过这个实体类我们可以发起XHR调用并且获得相应。
2) 第二行用来设置我们的请求,methodType
用来设定请求的类型:POST,GET,PUT,DELETE
等,GET/POST
是最常见的。URL
就是我们调用的服务地址,async
设定是否是异步,默认为true
,如果不为true
,那么send()
方法将不会return
直到服务端有响应回访。
3) 最后一行就是实发出请求,浏览器会创建一个HTTPRequest并发送去服务端。xhr会在请求上保存所有的信息,例如HTTPConnection状态,HTTPResponse状态以及状态码。
如果需要两个Ajax调用,就要重复这三行代码(当然封装了就更好了)。
Ajax状态改变函数监听
为了获悉到实时的访问状态,我们需要监听onreadystatechange
事件:1
2
3
4
5
6
7
8
9
10
11xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log('reaceived the response', xhr.responseText)
} else {
console.log('error in processing the request')
}
} else {
console.log('waiting for the response finish')
}
}
当HTTPConnection状态发生改变的时候都会调用一次onreadystatechange
,并且可以读取到xhr当前的状态readyState
,主要状态如下:
0: 请求未初始化
1: 服务器连接已建立
2: 请求已接收
3: 请求处理中
4: 请求已完成,且响应已就绪
当状态为4
的时候,就说明了响应结果已经返回了并且可以去判断请求的状态status
(200,404,500等等)了。当xhr.readyState === 4 && xhr.status === 200
的时候,就可以去读取xhr.responseText
的内容了,注意的是,responseText
的格式可能是XML,JSON,plain text,二进制或其他,这就要取决于客户端和浏览器之间的约定了。
完整的Ajax调用:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20function ajaxCall(url, { methodType = 'GET' }) {
const xhr = new XMLHttpRequest()
xhr.open(methodType, url, true)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log('xhr done successfully')
const resp = JSON.parse(xhr.responseText)
} else {
console.log('reqeust failed')
}
} else {
console.log('reqeust pending')
}
}
console.log('reqeust sent succesfully')
}
ajaxCall('https://test.com', { methodType: 'Get' })
那么POST请求呢
对于GET
来说,参数可以直接加在URL上没难度,那么POST
呢?
实际上xhr.send(...)
的参数就是接受要传出去的参数,对于POST
请求我们可以这么做:1
2
3... // 省略初始化
xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded') // 设定POST请求的请求头格式
xhr.send(`id=${id}`) // 发送带参数的请求
我们需要回调
为了看起来方便,暂时我们也只考虑GET
请求,也就是用回上面的完整代码。
当我们封装好ajax请求函数之后,我们需要一个机制,可以让函数通知我们请求已经结束了/发生异常了,首先我们选择JS的基础回调函数来实现:
1 | function ajaxCall(url, { methodType = 'GET' }, callback) { |
这样就可以在我们正常的流程中获取到请求的返回结果了。
加上Promise更实用
很多时候,请求是嵌套的,或者需要其他耗时的操作,容易造成回调地狱,这时候就可以用Promise
来帮助我们解决这个问题。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24function ajaxCall(url, { methodType = 'GET' }, callback) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open(methodType, url, true)
xhr.send()
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log('xhr done successfully')
const resp = JSON.parse(xhr.responseText)
resolve(resp)
} else {
console.log('reqeust failed')
reject(xhr.status)
}
} else {
console.log('reqeust pending')
}
}
console.log('reqeust sent succesfully')
})
}
let res = await ajaxCall('https://test.com', { methodType: 'Get' })
个人更喜欢使用async/await
,来到这一步,一个异步的ajax请求函数(非完整)的到此结束了。
等等,跨域问题呢?
实际上,XMLHttpRequest
应对跨域问题就两种解决方案:
- CORS,全称是“跨域资源共享”(Cross-origin resource sharing)。详细标准的内容可以看这篇文章
- JSONP,这个方案虽然可以支持更古老的浏览器,但是缺点是:
1) 只支持`GET`请求 2) 没有错误处理机制,发生错误的时候要么是跨域错误,要么是404连接错误。 3) 脆弱性,因为对返回的代码绝对的信任,所以进一步暴露了CSRF漏洞。
因此建议还是使用第一种方案,当然这种方案更重要的是和后端同事沟通好。
总结
到这里未知,一个Ajax的内容就基本差不多了,而fetch
相关的话可以看我的另一篇文章。好了到此结束。