func-package

函数库, 面向复杂业务场景的 js 类库

XMLHttpRequest API

使用 XMLHttpRequest(XHR) 对象可以与服务器交互。您可以从 URL 获取数据,而无需让整个的页面刷新。

Ajax(Asynchronous JavaScript and XML)是一系列 Web 开发技术的集合,使用很多的 Web 技术在客户端开发异步 Web 应用。利用 Ajax,Web 应用可以异步的发送数据获取数据,而不干扰现有页面的显示和行为。通过解耦数据接口层和展现层,Ajax 允许 Web 页面或者其他扩展的 Web 应用动态的改变数据而不用重新加载整个页面。实现通常选择 JSON 代替 XML,因为更接近 JavaScript。

EventTarget <- XMLHttpRequestEventTarget <- XMLHttpRequest

基本用法

const xhr = new XMLHttpRequest();

原型属性

此接口继承了 XMLHttpRequestEventTargetEventTarget 的属性。

属性说明类型
onreadystatechangereadyState 属性发生改变时,设定的回调函数会被调用Function
readyState(只读) 用于表示请求的五种状态unsigned short
response(只读) 用于获取整个响应实体,响应体的类型由 responseType 来指定Blob
ArrayBuffer
Document
JSON
String
null(请求未完成或失败)
responseText(只读) 用于获取请求的响应文本DOMString
null(请求未完成或失败)
responseType用于设置该值能够改变响应类型XMLHttpRequestResponseType
status(只读)用于获取请求的 响应状态码unsigned short
statusText(只读)用于获取请求的 响应状态信息,包含一个状态码和消息文本DOMString
timeout用于表示请求 最大请求时间(毫秒),若超出该时间,请求会自动终止unsigned long
upload(只读) 用于在 upload 上添加一个 事件监听 来跟踪上传过程XMLHttpRequestUpload
withCredentials用于指定跨域 Access-Control 请求是否应当带有授权信息,如 Cookie 或授权首部字段Boolean

onreadystatechange

使用示例:

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://developer.mozilla.org/', true);
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
console.log(xhr.responseText);
}
};
xhr.send();

readyState

状态描述
0UNSENT请求代表被创建,但尚未调用 open 方法
1OPENEDopen 方法已经被调用
2HEADERS_RECEIVEDsend 方法已被调用,并且头部和状态已经可访问
3LOADING下载中(responseText 属性已经包含部分数据)
4DONE下载操作已完成

原型方法

下列原型方法按照请求的生命周期设定:

open

XMLHttpRequest.open 方法用于初始化一个请求,。

语法:

xhr.open(method, url);
xhr.open(method, url, async);
xhr.open(method, url, async, user);
xhr.open(method, url, async, user, password);

参数:

  • method:请求方法,如 GET、POST、PUT、DELETE
  • url:请求的 URL 地址
  • async:一个可选的布尔值参数,默认值为 true,表示执行异步操作。如果值为 false,则 send 方法不会返回任何东西,直到接收到了服务器的返回数据
  • user:用户名(可选参数),用于授权。默认参数为空字符串
  • password:密码(可选参数),用于授权。默认参数为空字符串

使用方法:

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://example.com/user');

注意事项:

  • 如果 method 不是有效的 HTTP 方法或 URL 地址不能被成功解析,将会抛出 SyntaxError 异常
  • 如果请求方法(不区分大小写)为 CONNECTTRACETRACK 将会抛出 SecurityError 异常

setRequestHeader

XMLHttpRequest.sentRequestHeader 方法用于设置 HTTP 请求头信息。

⚠️ 注意:在这之前,你必须确认已经调用了 open 方法打开了一个 URL

语法:

xhr.setRequestHeader(header, value);

参数:

  • header:请求头名称
  • value:请求头值

overrideMimeType

XMLHttpRequest.overrideMimeType 方法用于重写由服务器返回的 MIME 类型,使服务端响应信息中传输的数据按照指定 MIME 类型处理。

例如,可以用于强制把响应流当做  text/xml  来解析,即使服务器没有指明数据是这个类型。

⚠️ 注意:这个方法必须在 send 之前被调用

send

XMLHttpRequest.send 方法用于发起网络请求。

  • 如果该请求是异步模式(默认),该方法会立即返回。
  • 相反,如果请求是同步模式,则直到请求的响应完全接受以后,该方法才会返回。

⚠️ 注意:所有相关的事件绑定必须在调用 send 方法之前进行

语法:

xhr.send(body);

参数:

  • body:在 XHR 请求中要发送的数据体,可以是以下其中某种类型
    • ArrayBuffer
    • Blob
    • Document
    • DOMString
    • FormData
    • URLSearchParams
    • USVString
    • null

使用方法:

const xhr = new XMLHttpRequest();
xhr.open('GET', '/server', true);
xhr.onload = function() {
// 请求结束后处理
};
xhr.send(null);
// xhr.send('Hello world!')
// xhr.send(new Blob())
// xhr.send(new Int8Array())
// xhr.send({ form: 'data' })
// xhr.send(document)

abort

XMLHttpRequest.abort 用于当请求已发送后立刻中止请求。当该方法执行后,readyState 将会被置为 XMLHttpRequest.UNSENT,并且请求的 status 属性置为 0。

getRequestHeader

XMLHttpRequest.getRequestHeader 方法用于获取指定响应头的值,如果响应头还没有被接收,或该响应头不存在,则返回 null

注意:使用该方法获取某些响应头时,浏览器会抛出异常,具体原因如下

  • W3C 的 XHR 标准中做了限制,规定客户端无法获取 response 中的 Set-CookieSet-Cookie2 这 2 个字段,无论是同域还是跨域请求
  • W3C 的 CORS 标准对于跨域请求也做了限制,规定对于跨域请求,客户端允许获取的响应头首部字段只限于简单的响应首部字段(常见的响应首部字段如下)
    • Expires
    • Cache-Control
    • Last-Modified
    • Pragma
    • Access-Control-Expose-Headers
    • Content-Language
    • Content-Type

getAllRequestHeaders

XMLHttpRequest.getAllResquestHeaders 方法用于获取所有响应头信息(响应头名和值),如果响应头还没有接收,则返回 null

⚠️ 注意:使用该方法获取的响应头首部字段与在开发者工具 Network 面板中看到的响应头不一致。

原型事件

事件名说明
loadstart用于当网络请求发送后触发,即调用 XMLHttpRequest.send 方法后触发,若未被调用则不会触发
load用于当请求完成时触发,此时 readyState 值为 DONE(4)
loaded用于当某个资源的加载进度停止后触发,例如已经触发 aborterrorload 事件之后
progress用于当请求接收到数据的时候被周期性触发。
abort用于当请求被暂停时触发。
timeout用于当请求超出最大时间时触发。
error用于当请求遭遇异常时触发

事件执行顺序:

  • 状态变更时触发
    • readystatechangereadyState 状态变更时触发
  • 请求发送前
    • xhr.loadstart
  • 请求发送阶段
    • xhr.upload.onloadstart
    • xhr.upload.onprogress
    • xhr.upload.onload
    • xhr.upload.onloadend
  • 请求发送结束,开始加载资源后
    • xhr.onprogress
    • xhr.onload
    • xhr.onloadend
  • 异常处理
    • xhr.onabort
    • xhr.timeout
    • xhr.error

应用示例

上传数据

下载数据

传输进度

XMLHttpRequest 对象传送数据的时,通过 progress 事件可以获取传输进度信息。

它分成上传和下载两种情况:

  • 下载的 progress 事件属于 XMLHttpRequest 对象
  • 上传的 progress 事件属于 XMLHttpRequest.upload 对象

我们先定义 progress 事件的回调函数:

function updateProgress(event) {
if (event.lengthComputable) {
var percentComplete = event.loaded / event.total;
}
}
xhr.onprogress = updateProgress;
xhr.upload.onprogress = updateProgress;

上面的代码中,event.total 是需要传输的总字节,event.loaded 是已经传输的字节。如果 event.lengthComputable 不为真,则 event.total 等于 0。

定时轮询

image-20241008231240497

定时轮询的基本思路就是浏览器每隔一段时间向浏览器发送 HTTP 请求,服务器端在收到请求后,不论是否有数据更新,都直接进行响应。这种方式实现的即时通信,本质上还是浏览器发送请求,服务器接受请求的一个过程,通过让客户端不断的进行请求,使得客户端能够模拟实时地收到服务器端的数据的变化。

这种方式的优点是比较简单,易于理解,实现起来也没有什么技术难点。缺点是显而易见的,这种方式由于需要不断的建立 HTTP 连接,严重浪费了服务器端和客户端的资源。尤其是在客户端,距离来说,如果有数量级想对比较大的人同时位于基于短轮询的应用中,那么每一个用户的客户端都会疯狂的向服务器端发送 HTTP 请求,而且不会间断。人数越多,服务器端压力越大,这是很不合理的。

因此短轮询不适用于那些同时在线用户数量比较大,并且很注重性能的 Web 应用。

const xhr = new XMLHttpRequest();
// 每 1000 毫秒向服务器发送一次轮询请求
setInterval(function() {
xhr.open('GET', '/server');
xhr.onreadystatechange = function() {};
xhr.send();
}, 1000);

长轮询

当服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将这个请求挂起,然后判断服务器端数据是否有更新。如果有更新,则进行响应,如果一直没有数据,则到达一定的时间限制(服务器端设置)才返回。 。 客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。

长轮询和短轮询比起来,明显减少了很多不必要的 http 请求次数,相比之下节约了资源。长轮询的缺点在于,连接挂起也会导致资源的浪费。

function checkUpdate() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/user');
xhr.onreadystatechange = function() {
checkUpdate();
};
xhr.send();
}

实现思路:

  1. 处理接收到的数据并启动下一轮检测更新
  2. 启动下一轮检测更新
  3. 发起首次更新请求

轮询与长轮询都是基于 HTTP 的,两者本身存在着缺陷:

  • 轮询需要更快的处理速度;
  • 长轮询则更要求处理并发的能力;

两者都是 被动型服务器 的体现:服务器不会主动推送信息,而是在客户端发送 AJAX 请求后进行返回的响应。而理想的模型是在服务器端数据有了变化后,可以主动推送给客户端,这种 主动型 服务器是解决这类问题的很好的方案。Web Sockets 就是这样的方案。

那么长轮询总是比定期轮询更好的选择?

除非消息到达率已知且不变,否则长轮询将始终提供更短的消息延迟。

另一方面,开销讨论需要更细微的观点。首先,请注意,每个传递的消息仍然引起相同的 HTTP 开销;每个新消息都是独立的 HTTP 请求。但是,如果消息到达率高,那么长时间轮询会比定期轮询发出更多的 XHR 请求!

长轮询通过最小化消息延迟来动态地适应消息到达速率,这是您可能想要的或可能不需要的行为。如果对消息延迟要求不高的话,则定时轮询可能是更有效的传输方式。例如,如果消息更新速率较高,则定时轮询提供简单的”消息聚合“机制(即合并一定时间内的消息),这可以减少请求数量并提高移动设备的电池寿命。

四种前端即时通讯技术比较:

  • 从兼容性考虑:短轮询 > 长轮询 > 长连接 SSE > WebSocket
  • 从性能方面考虑:WebSocket > 长连接 SSE > 长轮询 > 短轮询

长连接


参考资料: