同步和非同步請求

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);
}

另見