同步和异步请求

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.apply(this, 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,并最终提供给回调函数(在本例中为 showMessage),该函数由函数 xhrSuccess 调用。

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 处理程序。

用法

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.html(主页面)

html
<!doctype html>
<html lang="en-US">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <title>MDN Example</title>
    <script>
      const worker = new Worker("myTask.js");
      worker.onmessage = (event) => {
        alert(`Worker said: ${event.data}`);
      };

      worker.postMessage("Hello");
    </script>
  </head>
  <body></body>
</html>

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 工作线程

将同步 XHR 用例适配到 Beacon API

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

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

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

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, false);

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

另请参阅