MDN: https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/readystatechange_event
转载:https://juejin.cn/post/6844903855872802830
浏览器在 XMLHttpRequest
类上定义了他们的 HTTP API。这个类的每一个实例都表示一个独立的请求/响应,并且这个对象的属性和方法允许指定请求细节和提取响应数据。W3C在 XMLHttpRequest
规范的基础上制定了2级 XMLHttpRequest
(下文简称 XHR2
)标准草案,且大部分浏览器都已经支持了。
在介绍 XMLHttpRequest
之前,我想先简单说一下HTTP请求组成部分和响应的组成部分
一个 HTTP 请求由4部分组成:
- HTTP 请求方法或‘动作’。
- 正在请求的URL。
- 一个可选的请求头集合,其中可能包括身份验证信息。
- 一个可选的请求主体。
服务器返回的 HTTP 响应包含3部分:
- 一个数字或文字组成的状态码,用来显示请求的成功或失败。
- 一个响应头集合。
- 响应主体。
HTTP 请求的各部分有指定的顺序:请求方法和URL首先到达,然后是请求头,最后是主体。XHMLHttpRequest
实现通常直到调用send()方法才开始启动网络。但 XMLHttpRequest
API的设计似乎使每个方法都将写入网络流。这意味着调用 XMLHttpRequest
上的方法的顺序必须匹配 HTTP 请求的架构。例如,setRequestHeader() 方法的调用必须在调用 open() 之后但是在 send() 之前,否则就会跑出异常。下面我门会按照这个顺序来介绍。
实例化
使用 XMLHttpRequest
API的第一件事就是先实例化;
var request = new XMLHttpRequest();
复制代码
我们也能重用已经存在的 XMLHttpRequest对象,但是这将会中止之前通过该对象发起的任何请求,但一般不会这么用
指定请求
request.open('POST', url);
复制代码
-
第一个参数指定HTTP方法或动作,注意: 不区分大小写,但通常都是使用大写来匹配HTTP协议。
- GET 用于常规的请求,它适用于当URL完全指定请求资源,当请求对服务器没有任何副作用以及当服务器的响应是可缓存时。
- POST 方法常用语HTML表单,他在请求主体中包含额外数据(表单数据)且这些数据场储存到服务器上的数据库中(副作用)。相同 URL 的重复请求从服务器得到的相应数据可能不同。同时缓存时不应该使用这个请求。
-
第二个参数是URL,这个URL可以是相对url也可以是绝对的url。
-
第三个参数用来设置请求的异步还是同步
- false表示同步
- true表示异步,默认true
-
如果请求一个受保护的URL,把用户和密码作为第4和第5个参数传递。
设置请求头
request.setRequestheader('Content-Type': 'text/plain');
复制代码
注意:
-
如果对相同的头调用
setRequestHeader()
多次,新值不会取代之前指定的值,相反,HTTP请求将包含这个头的多个副本或这个头将指定多个值。 -
setRequestHeader()
没有办法设置下面的header
,XMLHttpRequest
将自动添加这些头而防止伪造他们。类似的,XMLHttpRequest
对象自动处理cookie
,连接时间,字符集和编码判断,所以无法向setRequestHeader()
传递这些头:
Accept-Charset | Accept-Encoding | Connection | Content-Length | Cookie |
Cookie2 | Content-Transfer-Encoding | Date | Expect | Host |
Keep-Live | Referer | TE | Trailer | Transfer-Encoding |
Upgrage | User-Agent | Via |
-
当
send()
方法传入XML
文档时,没有指定Content-Type
,XMLHttpRequest
会自动设置一个合适的头。类似的如果给send()
传入一个字符串但没有指定Content-Type
,那么XMLHttpRequest
将会自动添加text/plain; charset=utf-8
头。 -
当使用
GET
方法时,不需要调用setRequestHeader()
这个方法,因为GET
请求只能进行url编码(application/x-www-form-urlencoded)
,而如果使用POST
方法且传递的参数是以 ‘&’ 和 ‘=’ 符号进行键值连接时,Content-Type
头必须设置application/x-www-form-urlencoded
。
发送请求
使用 XMLHttpRequest
发起HTTP请求的最后一步就是指定可选的请求主体并向服务器发送它:
request.send(null);
复制代码
- GET 请求绝对没有主体,所以应该传递null或省略这个参数。
- POST 请求通常拥有主体,同时它应该匹配使用
setRequestHeader()
指定的Content-type
头。
XMLHttpRequest
属性
readystatechange
XMLHttpRequest
对象通常异步使用:发送请求后,send()
方法立即返回,直到响应返回。为了在响应准备就绪的时候得到通知,必须监听 XMLHttpRequest
对象上的 readystatechange
事件。
readyState
它是一个整数, 他指定了 HTTP
请求的状态。
- 0: 初始化状态。 xhr 对象已创建或已被
abort()
方法重置。 - 1:
open()
方法已调用,请求连接已经建立。但是send()
方法未调用,请求数据未发送。 - 2:
send()
方法已调用,HTTP
请求已发送到 Web 服务器。接收到头信息 - 3: 所有响应头部都已经接收到。响应体开始接收但未完成。
- 4:
HTTP
响应已经完全接收
status
服务器返回的http状态码,当 readyState 小于 3 的时候读取这一属性会导致一个异常。
- 200 表示成功
- 404 表示'Not Found'错误
statusText
以数字和文字的形式返回 HTTP 状态码。
- status === 200 statusText 为 'OK'
- status === 404 statusText 为 'Not Found'
getRequestHeader()/getAllRequestHeaders()
使用这两个方法都可以查询到响应头。XMLHttpRequest
会自动处理 cookie
,他会从 getAllRequestHeaders()
头返回集中过滤掉 cookie
头。而如果给 getRequestHeader()
传递 Set-Cookie
和 Set-Cookie2
则会返回 null
。
responseText
responseText
接受到服务器的相应数据 返回的值是一个json字符串 通过 JSON.parse(xhr.responseText)
可以得到数据对象
- readyState < 3 responseText 为空字符串
- readyState = 3 responseText 为已经接收到数据部分
- readyState = 4 responseText 为接受到了所有的相应部分
responseXML
responseXML属性可以得到 XML 的 Document形式的数据。
XHR2新增的事件集
XHR2规范定义了很多有用的事件集,在这个新的事件模型中,XMLHttpRequest
对象在请求的不同阶段触发不同类型的事件,所以它不再需要检查 readyState
属性
。
-
onloadstart
: 当调用send()
时,触发单个loadstart
事件 -
onprogress
: xhr对象会发生progress
事件,通常每隔50ms左右触发一次,所以可以使用这个事件给用户反馈请求的进度。如果请求快速完成,他可能不会触发progress
事件。注意这里的progress
是下载的进度,xhr2 额外的定义了上传upload
属性,用来定义上传的相关事件。 -
onload
: 当事件完成时,触发load
事件,load
事件的处理程序应该检查xhr对象的status
状态码来确定收到的是 200 还是 404 -
ontimeout
: 如果请求超时,会触发timeout
事件。 -
onerror
: 大多重定向这样的网络错误会阻止请求的完成,但这些情况发生时会触发error
事件。 -
onabort
: 如果请求中止,会触发abort
事件。 -
onloadend
: 对于任何具体的请求,浏览器将只会触发load/abort/timeout/error
事件中的一个。一旦这些事件触发以后,浏览器将会触发loadend
事件。
注意: 上面的这些事件我们可以通过 xhr.addEventListener() 方法进行监听。
progress事件中有三个属性需要讲解一下:
-
loaded
: 目前传输的字节数值 -
total
: 传输的数据的整体长度(单位字节),Content-Length == total
,如果不知道内容的长度则total == 0
; -
lengthComputable
: 如果知道内容的长度则lengthComputable == true
, 否则 false
xhr.onprogress = function(event) {
if (event.lengthComputable) {
const progress = event.loaded / event.total * 100;
}
}
复制代码
XHR2
新增的 upload
属性
XHR2
中新增了一个 upload
属性,这个属性值是一个对象,他定义了 addEventListener()
和 整个 progress
事件集合,比如说 onprogress
和 onload
。(但 upload
没有定义 onreadystatechange
属性,upload
仅能触发新的事件类型)。
-
onloadstart
: 和XMLHttpRequest
中的loadstart
事件一样。 -
onprogress
: 和XMLHttpRequest
中的progress
事件一样。 -
onload
: 和XMLHttpRequest
中的load
事件一样。 -
ontimeout
: 和XMLHttpRequest
中的timeout
事件一样。 -
onerror
: 和XMLHttpRequest
中的error
事件一样。 -
onabort
: 和XMLHttpRequest
中的abort
事件一样。 -
onloadend
: 和XMLHttpRequest
中的loadend
事件一样。
注意
upload
属性上定义的事件主要用在上传文件时。我们可以使用 upload
上的 onloadstart,onprogress
分别监听文件开始上传和上传过程中进度的变化。
对于文件上传,我们如何设置请求头??
const input = document.getElementsByTagName('input')[0];
input.addEventListener('change', function() {
var file = this.files[0];
if (!file) return;
var xhr = new XMLHttpRequest();
xhr.open('POST', url);
xhr.send(file);
}, false);
复制代码
文件类型是更普通的二进制大对象 Blob
类型中的一个字类型。XHR2
允许向 send()
方法传入任何 Blob
对象。如果没有显示的设置 Content-Type
头,这个 Blob
对象的 type
属性用于设置待上传的 Content-Type
头。
multipart/form-data请求
XHR2
定义了新的 FormData
API, 它容易实现多部分请求主体。首先,使用 FormData()
构造函数创建 FormData
对象,然后按需多次调用这个对象的 append()
方法把个体部分(string/File/Blob对象)
添加到请求中。最后把 FormData
对象传递给 send()
方法。send()
方法将对请求定义合适的边界字符串和设置 Content-Type
头。
中止请求和超时
中止请求
通过调用 XMLHttpRequest
对象的 abort()
方法来取消正在进行的 HTTP
请求。当调用 abort()
方法后会触发 xhr 对象的 onabort
事件。
超时
XHR2
定义了 timeout
属性来指定请求自动终止的毫秒数。同时也定义了 timeout
事件,当超时发生时触发。
demo
// 封装一个request方法
const request = (url, formData, cb) => {
// 初始化
const xhr = new XMLHttpRequest();
// 定义请求的方法/动作和url
xhr.open('POST', url);
// 设置超时时间,单位是毫秒
xhr.timeout = 2000;
xhr.ontimeout = function() {
console.log('timeout');
};
// 开始上传
xhr.upload.onloadstart = function() {
console.log('开始上传');
};
// 上传的进度
xhr.upload.onprogress = function(event) {
// 只有当 lengthComputable 为true是,loaded 才有值
if (event.lengthComputable) {
const value = Math.ceil((event.loaded / event.total) * 100);
cb && cb({
status: 'loading',
progress: value,
data: null,
});
}
};
// 监听事件完成, 完成并不一定代表请求成功,所以需要判断 status 状态码
xhr.onload = function() {
const resp: Response = {
status: 'success',
progress: 100,
data: null,
};
if (xhr.status === 200) {
resp.data = JSON.parse(xhr.responseText);
cb && cb(resp);
} else {
resp.status = 'error';
cb && cb(resp);
}
};
xhr.onerror = function() {
cb && cb({
status: 'error',
progress: 0,
data: null,
});
};
xhr.onabort = function() {
console.log('onabort');
};
xhr.onloadend = function() {
console.log('上传结束');
};
xhr.send(formData);
return xhr;
};
复制代码
HTTP跨域请求
作为同源策略的一部分,XMLHttpRequest
对象通常仅可以发起和文档具有相同服务器的 HTTP
请求。这个限制关闭了安全里漏洞,但同时也阻止了大量可使用的跨域请求。好在 XHR2
通过在 HTTP
响应中选择发送合适的 CORS(Cross-Origin Resource Sharing,跨域资源共享)
允许跨域访问网站。在日常开发中使用跨域请求并不需要进行的额外的其他设置,只要浏览器支持 CORS
跨域请求就行。 虽然实现 CORS
支持跨域的请求工作不需要做任务的事情,但有一些安全细节需要了解:
- 如果
xhr.open()
方法传入第四和第五个参数(用户名和密码)时,将不会通过跨域请求发送 - 跨域请求默认情况是不会携带
cookie
的。如果需要携带cookie
,那么可以在调用send()
方法之前设置XMLHttpRequest
的withCredentials
属性为true
- 监测浏览器是否支持
CORS
跨域请求,可以直接通过检测XMLHttpRequest
的withCredentials
的属性是否存在即可。
注意:XMLHttpRequest
的跨域请求同样包含简单请求和非简单请求,非简单请求又会进行预检请求,具体 CORS
的相关知识可以查看之前的分享的文章点击这里。
总结
XMLHttpRequest
API非常的好用,而且目前市面上的主浏览器也基本上都支持。相比 fetch
而言,兼容性肯定是更胜一筹,唯一不足的是不支持 Promise
,但是这也难不倒我们程序员,自己封装一层就可以了。更为重要的是 XMLHttpRequest
支持超时设置和中止请求,还有进度事件,这些都是 'fetch' 所不具备的。