1. 程式人生 > >詳細解讀XMLHttpRequest(一)同步請求和非同步請求

詳細解讀XMLHttpRequest(一)同步請求和非同步請求

XMLHttpRequest 讓傳送一個HTTP請求變得非常容易。你只需要簡單的建立一個請求物件例項,開啟一個URL,然後傳送這個請求。當傳輸完畢後,結果的HTTP狀態以及返回的響應內容也可以從請求物件中獲取。

通過XMLHttpRequest生成的請求可以有兩種方式來獲取資料,非同步模式或同步模式。請求的型別是由這個XMLHttpRequest物件的open()方法的第三個引數async的值決定的。如果該引數的值為false,則該XMLHttpRequest請求以同步模式進行,否則該過程將以非同步模式完成。

兩種通訊模式:同步和非同步請求:
同步請求
主執行緒中的同步請求會阻塞頁面,由於對使用者體驗的糟糕效果,部分最新瀏覽器在主執行緒上的同步請求已經被棄用。在極少數情況下,使用同步模式的XMLHttpRequests會比使用非同步模式更適合。

  1. 在Worker中使用XMLHttpRequest時,同步請求比非同步請求更適合。
    主頁中程式碼:

    <script type="text/javascript">
      var oMyWorker = new Worker("myTask.js");  
      oMyWorker.onmessage = function(oEvent) {  
        alert("Worker said: " + oEvent.data);
      };
      oMyWorker.postMessage("Hello");
    </script>
    myFile.txt ( XMLHttpRequest物件同步請求的檔案):
    Hello World!!

    包含了Worker程式碼:myTask.js

    self.onmessage = function (oEvent) {
      if (oEvent.data === "Hello") {
    var oReq = new XMLHttpRequest();
    oReq.open("GET", "myFile.txt", false);  // 同步請求
    oReq.send(null);
    self.postMessage(oReq.responseText);
      }
    };
    注意: 由於使用了Worker,所以該請求實際上也是非同步的.
    可以使用類似的方法,讓指令碼在後臺與伺服器互動,預載入某些內容.檢視使用web workers瞭解更多詳情
  2. 不得不使用同步請求的情況
    在少數情況下,只能使用同步模式的XMLHttpRequest請求.比如在 window.onunload和window.onbeforeunload 事件處理函式中。在頁面unload事件處理函式中使用非同步的XMLHttpRequest會引發這樣的問題:當響應返回之後,頁面已經不復存在,所有變數和回撥函式也已經銷燬.結果只能引起一個錯誤 ,“函式未定義”。解決辦法是在這裡使用同步模式的請求,這樣的話,當請求完成之前,頁面不會被關閉.

    window.onbeforeunload = function () {
      var oReq = new XMLHttpRequest();
      oReq.open("GET", "logout.php?nick=" + escape(myName), false);  // 同步請求
      oReq.send(null);
      if (oReq.responseText.trim() !== "已退出"); {  // "已退出"是返回的資料
    return "退出失敗,您想手動執行退出嗎?";
      }
    };
    非同步請求
    使用非同步模式的話,當資料完全請求回來以後,會執行一個指定的回撥函式, 在執行請求的同時,瀏覽器可以正常的執行其他事務的處理。
  3. 例子: 建立一個標準的方法來讀取外部檔案
    在一些需求情況下,必須讀取多個外部檔案. 這是一個標準的函式. 該函式使用XMLHttpRequest物件進行非同步請求.而且可以為每個檔案讀取完成後指定不同的回撥函式.

    function loadFile (sURL, timeout, fCallback /*, 傳入引數1, 傳入引數2, 等 */) {
      var aPassArgs = Array.prototype.slice.call(arguments, 3), oReq = new XMLHttpRequest();
      oReq.ontimeout = function() {
    console.log("請求超時.");
      }
      oReq.onreadystatechange = function() {
    if (oReq.readyState === 4) { 
      if (oReq.status === 200) {
        fCallback.apply(oReq, aPassArgs);
      } else {
        console.log("Error", oReq.statusText);
      }
    }
      };
      oReq.open("GET", sURL, true);
      oReq.timeout = timeout;
      oReq.send(null);
    }

    loadFile函式的用法:

    function showMessage (sMsg) {
      alert(sMsg + this.responseText);
    }
    loadFile("message.txt", 200, showMessage, "New message!\\n");
    第1行定義一個函式,當檔案讀取完畢後,fCallback函式會以第3個引數以後的所有引數為自己的引數來被呼叫.
    第3行使用一個超時設定,來避免你的程式碼為了等候讀取請求的返回資料長時間執行,通過為XMLHttpRequest物件的timeout 屬性賦值來指定
    第6行為onreadystatechange事件控制代碼指定了回撥函式,函式在每次執行時,檢查請求是否結束(請求狀態為4),如果是的話,判斷請求是否成功(HTTP狀態嗎是否為200),如果是的話,輸出頁面原始碼,如果請求出現了錯誤,輸出錯誤資訊.
    第15行指定第三個引數為true,表示該請求應該以非同步模式執行.
  4. 例子: 使用非同步請求,不使用閉包.

    function switchXHRState() {
      switch (this.readyState) {
    case 0: console.log("還沒呼叫open()方法."); break;
    case 1: console.log("還沒呼叫send()方法."); break;
    case 2: console.log("已經呼叫send()方法,響應頭和響應狀態已經返回."); break;
    case 3: console.log("下載中,已經得到部分響應實體."); break;
    case 4: console.log("請求完成!"); this.callback.apply(this, this.arguments);
      }
    };
    function loadFile (sURL, fCallback /*, 傳入引數1, 傳入引數2, 等 */) {
      var oReq = new XMLHttpRequest();
      oReq.callback = fCallback;
      oReq.arguments = Array.prototype.slice.call(arguments, 2);
      oReq.onreadystatechange = switchXHRState;
      oReq.open("GET", sURL, true);
      oReq.send(null);
    }

    使用 bind:

    function switchXHRState(fCallback, aArguments) {
      switch (this.readyState) {
    case 0: console.log("還沒呼叫open()方法."); break;
    case 1: console.log("還沒呼叫send()方法."); break;
    case 2: console.log("已經呼叫send()方法,響應頭和響應狀態已經返回."); break;
    case 3: console.log("下載中,已經得到部分響應實體."); break;
    case 4: console.log("請求完成!"); fCallback.apply(this, aArguments);
      }
    };
    function loadFile (sURL, fCallback /*, 傳入引數1, 傳入引數2, 等 */) {
      var oReq = new XMLHttpRequest();
      oReq.onreadystatechange = switchXHRState.bind(oReq, fCallback, Array.prototype.slice.call(arguments, 2));
      oReq.open("GET", sURL, true);
      oReq.send(null);
    }