同步和异步请求

XMLHttpRequest 支持同步和异步通信。但出于性能考虑,通常应该优先使用异步请求而非同步请求。

同步请求会阻塞代码执行,导致屏幕“冻结”,用户体验无响应。

异步请求

如果您使用异步 XMLHttpRequest,当数据接收完成后,您会收到一个回调。这允许浏览器在处理您的请求时继续正常工作。

示例:将文件发送到控制台日志

这是异步 XMLHttpRequest 最简单的用法。

js
const xhr = new XMLHttpRequest();
xhr.open("GET", "/bar/foo.txt", true);
xhr.onload = (e) => {
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      console.log(xhr.responseText);
    } else {
      console.error(xhr.statusText);
    }
  }
};
xhr.onerror = (e) => {
  console.error(xhr.statusText);
};
xhr.send(null);

xhr.open 行将其第三个参数指定为 true,表示请求应异步处理。

然后,我们创建一个事件处理函数对象,并将其分配给请求的 onload 属性。此处理程序检查请求的 readyState 以判断事务是否完成;如果完成且 HTTP 状态为 200,则处理程序将转储接收到的内容。如果发生错误,则显示错误消息。

xhr.send 调用实际启动请求。每当请求的状态发生变化时,都会调用回调例程。

示例:编写一个读取外部文件的函数

在某些情况下,您必须读取多个外部文件。这是一个标准函数,它异步使用 XMLHttpRequest 对象,以便将读取文件的内容切换到指定的侦听器。

js
function xhrSuccess() {
  this.callback(...this.arguments);
}

function xhrError() {
  console.error(this.statusText);
}

function loadFile(url, callback, ...args) {
  const xhr = new XMLHttpRequest();
  xhr.callback = callback;
  xhr.arguments = args;
  xhr.onload = xhrSuccess;
  xhr.onerror = xhrError;
  xhr.open("GET", url, true);
  xhr.send(null);
}

用法

js
function showMessage(message) {
  console.log(`${message} ${this.responseText}`);
}

loadFile("message.txt", showMessage, "New message!\n\n");

实用函数 loadFile 的签名声明了 (i) 要读取的目标 URL(通过 HTTP GET 请求),(ii) 在 XHR 操作成功完成后要执行的函数,以及 (iii) 一系列附加参数,这些参数通过 XHR 对象(通过 arguments 属性)传递给成功回调函数。

我们首先声明一个函数 xhrSuccess,在 XHR 操作成功完成后调用。它反过来调用 loadFile 函数调用中指定的(在本例中为 showMessage)已分配给 XHR 对象属性的回调函数。传递给 loadFile 函数调用的附加参数(如果有)会“应用于”回调函数的运行。xhrError 函数在 XHR 操作未能成功完成后调用。

我们将作为 loadFile 第二个参数提供的成功回调存储在 XHR 对象的 callback 属性中。从第三个参数开始,loadFile 的所有其余参数使用 rest 参数 语法收集,分配给变量 xhrarguments 属性,传递给成功回调函数 xhrSuccess,并最终提供给由 xhrSuccess 函数调用的回调函数(在本例中为 showMessage)。

xhr.open 调用将其第三个参数指定为 true,表示请求应异步处理。

最后,xhr.send 实际启动请求。

示例:使用超时

您可以使用超时来防止代码在等待读取完成时挂起。这是通过设置 XMLHttpRequest 对象上的 timeout 属性值来实现的,如下面的代码所示。

js
function loadFile(url, timeout, callback, ...args) {
  const xhr = new XMLHttpRequest();
  xhr.ontimeout = () => {
    console.error(`The request for ${url} timed out.`);
  };
  xhr.onload = () => {
    if (xhr.readyState === 4) {
      if (xhr.status === 200) {
        callback.apply(xhr, args);
      } else {
        console.error(xhr.statusText);
      }
    }
  };
  xhr.open("GET", url, true);
  xhr.timeout = timeout;
  xhr.send(null);
}

请注意,通过设置 ontimeout 处理程序添加了处理“timeout”事件的代码。

用法

js
function showMessage(message) {
  console.log(`${message} ${this.responseText}`);
}

loadFile("message.txt", 2000, showMessage, "New message!\n");

在这里,我们指定了 2000 毫秒的超时。

同步请求

警告: 同步 XHR 请求经常导致 Web 挂起,尤其是在网络条件差或远程服务器响应缓慢的情况下。同步 XHR 现在已弃用,应避免使用,而应优先使用异步请求。

所有新的 XHR 功能,如 timeoutabort,不允许用于同步 XHR。这样做将引发 InvalidAccessError

示例:HTTP 同步请求

此示例演示了如何进行简单的同步请求。

js
const request = new XMLHttpRequest();
request.open("GET", "/bar/foo.txt", false); // `false` makes the request synchronous
request.send(null);

if (request.status === 200) {
  console.log(request.responseText);
}

request.send 调用发送请求。null 参数表示 GET 请求不需要任何正文内容。

if 语句在事务完成后检查状态码。如果结果是 200 — HTTP 的“OK”结果 — 则将文档的文本内容输出到控制台。

示例:Worker 中的同步 HTTP 请求

在少数情况下,同步请求通常不会阻塞执行,那就是在 Worker 中使用 XMLHttpRequest

example.js(在主页面上调用的脚本)

js
const worker = new Worker("myTask.js");
worker.onmessage = (event) => {
  console.log(`Worker said: ${event.data}`);
};

worker.postMessage("Hello");

myFile.txt(同步 XMLHttpRequest 调用的目标)

Hello World!!

myTask.jsWorker

js
self.onmessage = (event) => {
  if (event.data === "Hello") {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", "myFile.txt", false); // synchronous request
    xhr.send(null);
    self.postMessage(xhr.responseText);
  }
};

注意: 由于使用了 Worker,因此效果是异步的。

此模式可能很有用,例如,为了在后台与服务器交互,或预加载内容。有关示例和详细信息,请参阅 使用 Web Workers

将同步 XHR 用例改编为 Beacon API

在某些情况下,同步使用 XMLHttpRequest 是不可替代的,例如在 unloadbeforeunloadpagehide 事件期间。您应该考虑使用带 keepalive 标志的 fetch() API。当 fetch 配合 keepalive 不可用时,您可以考虑使用 navigator.sendBeacon() API,它可以支持这些用例,同时通常能提供良好的用户体验。

以下示例显示了理论上的分析代码,它尝试在 unload 处理程序中使用同步 XMLHttpRequest 将数据提交到服务器。这会导致页面卸载延迟。

js
window.addEventListener("unload", logData);

function logData() {
  const client = new XMLHttpRequest();
  client.open("POST", "/log", false); // third parameter indicates sync xhr. :(
  client.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
  client.send(analyticsData);
}

使用 sendBeacon() 方法,数据将在用户代理有机会这样做时异步传输到 Web 服务器,而不会延迟卸载或影响下一次导航的性能。

以下示例显示了理论上的分析代码模式,该模式使用 sendBeacon() 方法将数据提交到服务器。

js
window.addEventListener("unload", logData);

function logData() {
  navigator.sendBeacon("/log", analyticsData);
}

另见