XMLHttpRequest
是一个内置的浏览器对象,允许使用JavaScript发出HTTP请求。
尽管名称中带有“ XML”一词,但它可以对任何数据进行操作,而不仅限于XML格式。我们可以上传/下载文件,跟踪进度等等。
现在,还有另一种更现代的方法fetch
在某种程度上过时了XMLHttpRequest
。
在现代网络开发XMLHttpRequest
中,出于以下三个原因:
历史原因:我们需要使用来支持现有脚本
XMLHttpRequest
。我们需要支持旧的浏览器,并且不需要polyfill(例如,使脚本很小)。
我们需要一些
fetch
尚无法做到的事情,例如跟踪上传进度。
基础
XMLHttpRequest具有两种操作模式:同步和异步。
首先让我们看看异步,因为它在大多数情况下都被使用。
要执行此请求,我们需要3个步骤:
创建
XMLHttpRequest
:let xhr = new XMLHttpRequest();
构造函数没有参数。
初始化它,通常在之后
new XMLHttpRequest
:xhr.open(method, URL, [async, user, password])
此方法指定请求的主要参数:
请注意,
open
仅配置请求,真正开始发送请求的是send
。method
– HTTP方法。通常"GET"
还是"POST"
。URL
–请求的URL(字符串)可以是URL对象。async
–如果显式设置为false
,则请求是同步的,我们稍后再讨论。user
,password
–基本HTTP身份验证的登录名和密码(如果需要)。发送请求。
xhr.send([body])
此方法打开连接并将请求发送到服务器。可选
body
参数包含请求正文。某些请求方法例如
GET
没有主体。其中一些类似的POST
使用body
将数据发送到服务器。我们稍后将看到示例。聆听
xhr
事件以作出回应。这三个事件使用最广泛:
xhr.onload = function() { alert(`Loaded: ${xhr.status} ${xhr.response}`);}; xhr.onerror = function() { // only triggers if the request couldn't be made at all alert(`Network Error`); }; xhr.onprogress = function(event) { // triggers periodically // event.loaded - how many bytes downloaded // event.lengthComputable = true if the server sent Content-Length header // event.total - total number of bytes (if lengthComputable) alert(`Received ${event.loaded} of ${event.total}`); };
load
–请求完成后(即使HTTP状态为400或500),并且响应已完全下载。error
–无法发出请求时,例如网络中断或无效的URL。progress
–在下载响应时定期触发,报告已下载了多少。
这是一个完整的例子。下面的代码/article/xmlhttprequest/example/load
从服务器上加载URL 并显示进度:
// 1. Create a new XMLHttpRequest object let xhr = new XMLHttpRequest(); // 2. Configure it: GET-request for the URL /article/.../load xhr.open('GET', '/article/xmlhttprequest/example/load'); // 3. Send the request over the network xhr.send(); // 4. This will be called after the response is received xhr.onload = function() { if (xhr.status != 200) { // analyze HTTP status of the response alert(`Error ${xhr.status}: ${xhr.statusText}`); // e.g. 404: Not Found } else { // show the result alert(`Done, got ${xhr.response.length} bytes`); // response is the server } }; xhr.onprogress = function(event) { if (event.lengthComputable) { alert(`Received ${event.loaded} of ${event.total} bytes`); } else { alert(`Received ${event.loaded} bytes`); // no Content-Length } }; xhr.onerror = function() { alert("Request failed"); };
服务器响应后,我们可以在以下xhr
属性中接收结果:
status
200,400,404等等这些都可以在非HTTP故障的情况下。
statusText
HTTP状态消息(字符串):通常
OK
用于200
,Not Found
for404
,Forbidden
for403
等。response
(旧脚本可能会使用responseText
)服务器响应主体。
我们还可以使用相应的属性指定超时:
xhr.timeout = 10000; // timeout in ms, 10 seconds
如果请求在给定时间内没有成功,它将被取消并timeout
触发事件。
网址搜索参数
要向URL中添加参数(例如?name=value
)并确保编码正确,我们可以使用URL对象:
let url = new URL('); url.searchParams.set('q', 'test me!'); // the parameter 'q' is encoded xhr.open('GET', url); // https://google.com/search?q=test+me%21
回应类型
我们可以使用xhr.responseType
property设置响应格式:
""
(默认)–以字符串形式获取,"text"
–作为字符串,"arraybuffer"
–获取为ArrayBuffer
(有关二进制数据,请参见ArrayBuffer,二进制数组一章),"blob"
–作为Blob
(获取二进制数据,请参见Blob章节),"document"
–作为XML文档获取(可以使用XPath和其他XML方法),"json"
–作为JSON获取(自动分析)。
例如,让我们以JSON作为响应:
let xhr = new XMLHttpRequest(); xhr.open('GET', '/article/xmlhttprequest/example/json'); xhr.responseType = 'json'; xhr.send(); // the response is {"message": "Hello, world!"} xhr.onload = function() { let responseObj = xhr.response alert(responseObj.message) // Hello, world! };
请注意:
在旧脚本中,您可能还会找到xhr.responseText
甚至xhr.responseXML
属性。
它们由于历史原因而存在,以获取字符串或XML文档。
就绪状态
XMLHttpRequest
在状态之间变化。当前状态可以通过访问 xhr.readyState
。
所有状态,如规范中所示:
UNSENT = 0; // initial state OPENED = 1; // open called HEADERS_RECEIVED = 2; // response headers received LOADING = 3; // response is loading (a data packed is received) DONE = 4; // request complete
一个XMLHttpRequest
物体移动它们的顺序0
→交通1
→交通2
→交通3
→交通...→交通3
→交通4
。3
每次通过网络接收到数据包时,状态都会重复。
我们可以使用readystatechange
事件跟踪它们:
xhr.onreadystatechange = function() { if (xhr.readyState == 3) { // loading } if (xhr.readyState == 4) { // request finished } };
您可以readystatechange
在非常古老的代码中找到侦听器,这是有历史原因的,因为曾经有一段时间没有load
其他事件。如今,load/error/progress
处理程序已弃用它。
中止要求
我们可以随时终止请求。的调用xhr.abort()
执行此操作:
xhr.abort(); // terminate the request
触发abort
事件,并xhr.status
变为0
。
同步请求
如果在open
方法中将第三个参数async
设置为false
,则同步进行请求。
换句话说,JavaScript执行在send()
接收到响应时暂停并恢复。有点像alert
或prompt
命令。
这是重写的示例,的第三个参数open
是false
:
let xhr = new XMLHttpRequest(); xhr.open('GET', '/article/xmlhttprequest/hello.txt', false); try { xhr.send(); if (xhr.status != 200) { alert(`Error ${xhr.status}: ${xhr.statusText}`); } else { alert(xhr.response); } } catch(err) { // instead of onerror alert("Request failed"); }
看起来不错,但是很少使用同步调用,因为它们会阻塞页面内JavaScript,直到加载完成。在某些浏览器中,无法滚动。如果同步呼叫花费太多时间,浏览器可能会建议关闭“挂起”的网页。
XMLHttpRequest
从的其他域请求或指定超时等许多高级功能对同步请求不可用。另外,如您所见,没有进度指示。
事实上,这种同步调用极少使用。
HTTP头
XMLHttpRequest
允许发送自定义header和从响应读取header。
HTTP header有3种方法:
setRequestHeader(name, value)
使用给定的
name
和设置请求headervalue
。例如:
xhr.setRequestHeader('Content-Type', 'application/json');
header 限制
几个标头仅由浏览器管理,例如
Referer
和Host
。完整列表在规范中。XMLHttpRequest
为了用户安全和请求的正确性,不允许手动更改它们。setRequestHeader 不是覆盖性
的另一个特点
XMLHttpRequest
是不能撤消setRequestHeader
。header 设置好后,就设置好了。其他调用会将信息添加到header中,而不是覆盖。
例如:
xhr.setRequestHeader('X-Auth', '123'); xhr.setRequestHeader('X-Auth', '456'); // the header will be:// X-Auth: 123, 456
getResponseHeader(name)
获取具有给定的响应标头
name
(Set-Cookie
和除外Set-Cookie2
)。例如:
xhr.getResponseHeader('Content-Type')
getAllResponseHeaders()
返回除
Set-Cookie
和之外的所有响应头Set-Cookie2
。header 作为一行返回,例如:
Cache-Control: max-age=31536000 Content-Length: 4260 Content-Type: image/png Date: Sat, 08 Sep 2012 16:53:16 GMT
标头之间的换行符始终是
"\r\n"
(不取决于OS),因此我们可以轻松地将其拆分为单独的标头。名称和值之间的分隔符始终是一个冒号,后跟一个空格": "
。这在规范中已解决。因此,如果要获取具有名称/值对的对象,则需要添加一些JS。
这样(假设两个标头具有相同的名称,则后一个标头将覆盖前一个标头):
let headers = xhr .getAllResponseHeaders() .split('\r\n') .reduce((result, current) => { let [name, value] = current.split(': '); result[name] = value; return result; }, {}); // headers['Content-Type'] = 'image/png'
POST,FormData
要发出POST请求,我们可以使用内置的FormData对象。
语法:
let formData = new FormData([form]); // creates an object, optionally fill from <form> formData.append(name, value); // appends a field
我们创建它,可以选择从表单中填写,append
如果需要,可以添加更多字段,然后:
xhr.open('POST', ...)
–使用POST
方法。xhr.send(formData)
将表单提交到服务器。
例如:
<form name="person"> <input name="name" value="John"> <input name="surname" value="Smith"> </form> <script> // pre-fill FormData from the form let formData = new FormData(document.forms.person); // add one more field formData.append("middle", "Lee"); // send it out let xhr = new XMLHttpRequest(); xhr.open("POST", "/article/xmlhttprequest/post/user"); xhr.send(formData); xhr.onload = () => alert(xhr.response); </script>
表单以multipart/form-data
编码形式发送。
或者,如果我们更喜欢JSON,则将其JSON.stringify
作为字符串发送。
只是不要忘记设置标头Content-Type: application/json
,许多服务器端框架都使用它自动解码JSON:
let xhr = new XMLHttpRequest(); let json = JSON.stringify({ name: "John", surname: "Smith"}); xhr.open("POST", '/submit') xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8'); xhr.send(json);
该.send(body)
方法非常简单。它几乎可以发送body
,包括Blob
和BufferSource
对象。
上传进度
该progress
事件仅在下载阶段触发。
那就是:如果有POST
什么事情,XMLHttpRequest
首先上传我们的数据(请求主体),然后下载响应。
如果我们要上传较大的内容,那么我们肯定会对跟踪上传进度更感兴趣。但是xhr.onprogress
对这里没有帮助。
还有一个没有方法的对象,专门用于跟踪上传事件:xhr.upload
。
它生成类似于的事件,xhr
但xhr.upload
仅在上传时触发它们:
loadstart
–开始上传。progress
–在上传期间定期触发。abort
–上传中止。error
–非HTTP错误。load
–上传成功完成。timeout
–上传超时(如果timeout
设置了属性)。loadend
–上传成功或失败。
处理程序示例:
xhr.upload.onprogress = function(event) { alert(`Uploaded ${event.loaded} of ${event.total} bytes`); }; xhr.upload.onload = function() { alert(`Upload finished successfully.`); }; xhr.upload.onerror = function() { alert(`Error during the upload: ${xhr.status}`); };
这是一个真实的示例:带有进度指示的文件上传:
<input type="file" onchange="upload(this.files[0])"> <script> function upload(file) { let xhr = new XMLHttpRequest(); // track upload progress xhr.upload.onprogress = function(event) { console.log(`Uploaded ${event.loaded} of ${event.total}`); }; // track completion: both successful or not xhr.onloadend = function() { if (xhr.status == 200) { console.log("success"); } else { console.log("error " + this.status); } }; xhr.open("POST", "/article/xmlhttprequest/post/upload"); xhr.send(file); } </script>
跨域请求
XMLHttpRequest
可以使用与fetch相同的CORS策略发出跨域请求。
就像一样fetch
,默认情况下它不会将cookie和HTTP授权发送到另一个来源。要启用它们,请设置xhr.withCredentials
为true
:
let xhr = new XMLHttpRequest(); xhr.withCredentials = true; xhr.open('POST', 'http://anywhere.com/request');...
摘要
GET-request的典型代码XMLHttpRequest
:
let xhr = new XMLHttpRequest(); xhr.open('GET', '/my/url'); xhr.send(); xhr.onload = function() { if (xhr.status != 200) { // HTTP error? // handle error alert( 'Error: ' + xhr.status); return; } // get the response from xhr.response }; xhr.onprogress = function(event) { // report progress alert(`Loaded ${event.loaded} of ${event.total}`); }; xhr.onerror = function() { // handle non-HTTP error (e.g. network down) };
实际上还有更多事件,列出(按生命周期顺序):
loadstart
–请求已开始。progress
–响应的数据包已到达,此刻整个响应主体都在response
。abort
–该请求已被呼叫取消xhr.abort(),不同于http request aborted
。error
–发生连接错误,例如域名错误。不会发生HTTP错误(例如404)。load
–请求已成功完成。timeout
–由于超时,请求被取消(仅在已设置的情况下发生)。loadend
-触发后load
,error
,timeout
或abort
。
在error
,abort
,timeout
,和load
事件是相互排斥的。其中只有一种可能发生。
最常用的事件是加载完成(load
),加载失败(error
),或者我们可以使用单个loadend
处理程序并检查请求对象的属性xhr
以查看发生了什么。
我们已经看到了另一个事件:readystatechange
。从历史上看,它早在规范制定之前就出现了。如今,无需使用它,我们可以用较新的事件替换它,但通常可以在旧脚本中找到它。
如果我们需要专门跟踪上传,那么我们应该在xhr.upload
对象上监听相同的事件。
这里我们补充一种情况
当我们发送一个异步请求,而请求还没得到响应的时候,页面被重定向了,上面生命周期的方法会被trigger吗?
这个跟各大浏览器的核心相关,不同浏览器在这点上有不同表现,如chrome上页面被重定向后是不会再trigger上面生命周期事件的,而在safari上,在 http 状态被aborted的时候,safari依然会触发其中的error事件。开发者面对这种不同机制带来的处理上的区别,只能耗费更多的时间用于兼容性调试上了。