如何使用JavaScript和Ajax發出非同步請求(二)

來源:互聯網
上載者:User
ajax|javascript|請求|非同步     開啟請求

  有了要串連的 URL 後就可以配置請求了。可以用 XMLHttpRequest 對象的 open() 方法來完成。該方法有五個參數:

  • request-type:發送請求的類型。典型的值是 GET 或 POST,但也可以發送 HEAD 請求。
  • url:要串連的 URL。
  • asynch:如果希望使用非同步串連則為 true,否則為 false。該參數是可選的,預設為 true。
  • username:如果需要身分識別驗證,則可以在此指定使用者名稱。該選擇性參數沒有預設值。
  • password:如果需要身分識別驗證,則可以在此指定口令。該選擇性參數沒有預設值。

  通常使用其中的前三個參數。事實上,即使需要非同步串連,也應該指定第三個參數為 “true”。這是預設值,但堅持明確指定請求是非同步還是同步的更容易理解。

  將這些結合起來,通常會得到 清單 9 所示的一行代碼。

  清單 9. 開啟請求

   function getCustomerInfo() {
     var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
     request.open("GET", url, true);
   }

  一旦設定好了 URL,其他就簡單了。多數請求使用 GET 就夠了(後面的文章中將看到需要使用 POST 的情況),再加上 URL,這就是使用 open() 方法需要的全部內容了。

  挑戰非同步性

  本系列的後面一篇文章中,我將用很多時間編寫和使用非同步代碼,但是您應該明白為什麼 open() 的最後一個參數這麼重要。在一般的請求/響應模型中,比如 Web 1.0,客戶機(瀏覽器或者本地機器上啟動並執行代碼)向伺服器發出請求。該請求是同步的,換句話說,客戶機等待伺服器的響應。當客戶機等待的時候,至少會用某種形式通知您在等待:

  • 沙漏(特別是 Windows 上)。
  • 旋轉的皮球(通常在 Mac 機器上)。
  • 應用程式基底本上凍結了,然後過一段時間游標變化了。

  這正是 Web 應用程式讓人感到笨拙或緩慢的原因 —— 缺乏真正的互動性。按下按鈕時,應用程式實際上變得不能使用,直到剛剛觸發的請求得到響應。如果請求需要大量伺服器處理,那麼等待的時間可能很長(至少在這個多處理器、DSL 沒有等待的世界中是如此)。

  而非同步請求不 等待伺服器響應。發送請求後應用程式繼續運行。使用者仍然可以在 Web 表單中輸入資料,甚至離開表單。沒有旋轉的皮球或者沙漏,應用程式也沒有明顯的凍結。伺服器悄悄地響應請求,完成後告訴原來的要求者工作已經結束(具體的辦法很快就會看到)。結果是,應用程式感覺不 那麼遲鈍或者緩慢,而是響應迅速、互動性強,感覺快多了。這僅僅是 Web 2.0 的一部分,但它是很重要的一部分。所有老套的 GUI 組件和 Web 設計範型都不能克服緩慢、同步的請求/響應模型。

  發送請求

  一旦用 open() 配置好之後,就可以發送請求了。幸運的是,發送請求的方法的名稱要比 open() 適當,它就是 send()。

  send() 只有一個參數,就是要發送的內容。但是在考慮這個方法之前,回想一下前面已經通過 URL 本身發送過資料了:

var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);

  雖然可以使用 send() 發送資料,但也能通過 URL 本身發送資料。事實上,GET 請求(在典型的 Ajax 應用中大約佔 80%)中,用 URL 發送資料要容易得多。如果需要發送安全資訊或 XML,可能要考慮使用 send() 發送內容(本系列的後續文章中將討論安全資料和 XML 訊息)。如果不需要通過 send() 傳遞資料,則只要傳遞 null 作為該方法的參數即可。因此您會發現在本文中的例子中只需要這樣發送請求(參見 清單 10)。

  清單 10. 發送請求

   function getCustomerInfo() {
     var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
     request.open("GET", url, true);
     request.send(null);
   }

 指定回調方法

  現在我們所做的只有很少一點是新的、革命性的或非同步。必須承認,open() 方法中 “true” 這個小小的關鍵字建立了非同步請求。但是除此之外,這些代碼與用 Java servlet 及 JSP、PHP 或 Perl 編程沒有什麼兩樣。那麼 Ajax 和 Web 2.0 最大的秘密是什麼呢?秘密就在於 XMLHttpRequest 的一個簡單屬性 onreadystatechange。

  首先一定要理解這些代碼中的流程(如果需要請回顧 清單 10)。建立其請求然後發出請求。此外,因為是非同步請求,所以 JavaScript 方法(例子中的 getCustomerInfo())不會等待伺服器。因此代碼將繼續執行,就是說,將退出該方法而把控制返回給表單。使用者可以繼續輸入資訊,應用程式不會等待伺服器。

  這就提出了一個有趣的問題:伺服器完成了請求之後會發生什麼?答案是什麼也不發生,至少對現在的代碼而言如此!顯然這樣不行,因此伺服器在完成通過 XMLHttpRequest 發送給它的請求處理之後需要某種指示說明怎麼做。

  現在 onreadystatechange 屬性該登場了。該屬性允許指定一個回呼函數。回調允許伺服器(猜得到嗎?)反向調用 Web 頁面中的代碼。它也給了伺服器一定程度的控制權,當伺服器完成請求之後,會查看 XMLHttpRequest 對象,特別是 onreadystatechange 屬性。然後調用該屬性指定的任何方法。之所以稱為回調是因為伺服器向網頁發起調用,無論網頁本身在做什麼。比方說,可能在使用者坐在椅子上手沒有碰鍵盤的時候調用該方法,但是也可能在使用者輸入、移動滑鼠、滾動螢幕或者點擊按鈕時調用該方法。它並不關心使用者在做什麼。

  這就是稱之為非同步原因:使用者在一層上動作表單,而在另一層上伺服器響應請求並觸發 onreadystatechange 屬性指定的回調方法。因此需要像 清單 11 一樣在代碼中指定該方法。

  清單 11. 設定回調方法

   function getCustomerInfo() {
     var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
     request.open("GET", url, true);
     request.onreadystatechange = updatePage;
     request.send(null);
   }

  需要特別注意的是該屬性在代碼中設定的位置 —— 它是在調用 send()之前 設定的。發送請求之前必須設定該屬性,這樣伺服器在回答完成請求之後才能查看該屬性。現在剩下的就只有編寫 updatePage() 方法了,這是本文最後一節要討論的重點。

  處理伺服器響應

  發送請求,使用者高興地使用 Web 表單(同時伺服器在處理請求),而現在伺服器完成了請求處理。伺服器查看 onreadystatechange 屬性確定要調用的方法。除此以外,可以將您的應用程式看作其他應用程式一樣,無論是否非同步。換句話說,不一定要採取特殊的動作編寫響應伺服器的方法,只需要改變表單,讓使用者訪問另一個 URL 或者做響應伺服器需要的任何事情。這一節我們重點討論對伺服器的響應和一種典型的動作 —— 即時改變使用者看到的表單中的一部分。

  回調和 Ajax

  現在我們已經看到如何告訴伺服器完成後應該做什麼:將 XMLHttpRequest 對象的 onreadystatechange 屬性設定為要啟動並執行函數名。這樣,當伺服器處理完請求後就會自動調用該函數。也不需要擔心該函數的任何參數。我們從一個簡單的方法開始,如 清單 12 所示。

  清單 12. 回調方法的代碼

<script language="javascript" type="text/javascript">
   var request = false;
   try {
     request = new XMLHttpRequest();
   } catch (trymicrosoft) {
     try {
       request = new ActiveXObject("Msxml2.XMLHTTP");
     } catch (othermicrosoft) {
       try {
         request = new ActiveXObject("Microsoft.XMLHTTP");
       } catch (failed) {
         request = false;
       } 
     }
   }

   if (!request)
     alert("Error initializing XMLHttpRequest!");

   function getCustomerInfo() {
     var phone = document.getElementById("phone").value;
     var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
     request.open("GET", url, true);
     request.onreadystatechange = updatePage;
     request.send(null);
   }

   function updatePage() {
     alert("Server is done!");
   }
</script>

  它僅僅發出一些簡單的警告,告訴您伺服器什麼時候完成了任務。在自己的網頁中實驗這些代碼,然後在瀏覽器中開啟(如果希望查看該例中的 XHTML,請參閱 清單 8)。輸入電話號碼然後離開該欄位,將看到一個彈出的警告視窗(如 圖 3 所示),但是點擊 OK 又出現了……

  圖 3. 彈出警告的 Ajax 代碼

  根據瀏覽器的不同,在表單停止彈出警告之前會看到兩次、三次甚至四次警告。這是怎麼回事呢?原來我們還沒有考慮 HTTP 就緒狀態,這是請求/響應迴圈中的一個重要部分。

  HTTP 就緒狀態

  前面提到,伺服器在完成請求之後會在 XMLHttpRequest 的 onreadystatechange 屬性中尋找要調用的方法。這是真的,但還不完整。事實上,每當 HTTP 就緒狀態改變時它都會調用該方法。這意味著什麼呢?首先必須理解 HTTP 就緒狀態。

  HTTP 就緒狀態表示請求的狀態或情形。它用於確定該請求是否已經開始、是否得到了響應或者請求/響應模型是否已經完成。它還可以協助確定讀取伺服器提供的響應文本或資料是否安全。在 Ajax 應用程式中需要瞭解五種就緒狀態:

  • 0:請求沒有發出(在調用 open() 之前)。
  • 1:請求已經建立但還沒有發出(調用 send() 之前)。
  • 2:請求已經發出正在處理之中(這裡通常可以從響應得到內容標題部)。
  • 3:請求已經處理,響應中通常有部分資料可用,但是伺服器還沒有完成響應。
  • 4:響應已完成,可以訪問伺服器響應並使用它。

  與大多數跨瀏覽器問題一樣,這些就緒狀態的使用也不盡一致。您也許期望任務就緒狀態從 0 到 1、2、3 再到 4,但實際上很少是這種情況。一些瀏覽器從不報告 0 或 1 而直接從 2 開始,然後是 3 和 4。其他瀏覽器則報告所有的狀態。還有一些則多次報告就緒狀態 1。在上一節中看到,伺服器多次調用 updatePage(),每次調用都會彈出警告框 —— 可能和預期的不同!

  對於 Ajax 編程,需要直接處理的惟一狀態就是就緒狀態 4,它表示伺服器響應已經完成,可以安全地使用響應資料了。基於此,回調方法中的第一行應該如 清單 13 所示。

  清單 13. 檢查就緒狀態

   function updatePage() {
     if (request.readyState == 4)
       alert("Server is done!");
   }

  修改後就可以保證伺服器的處理已經完成。嘗試運行新版本的 Ajax 代碼,現在就會看到與預期的一樣,只顯示一次警告資訊了。

  HTTP 狀態代碼

  雖然 清單 13 中的代碼看起來似乎不錯,但是還有一個問題 —— 如果伺服器響應請求並完成了處理但是報告了一個錯誤怎麼辦?要知道,伺服器端代碼應該明白它是由 Ajax、JSP、普通 HTML 表單或其他類型的代碼調用的,但只能使用傳統的 Web 專用方法報告資訊。而在 Web 世界中,HTTP 代碼可以處理請求中可能發生的各種問題。

  比方說,您肯定遇到過輸入了錯誤的 URL 請求而得到 404 錯誤碼的情形,它表示該頁面不存在。這僅僅是 HTTP 要求能夠收到的眾多錯誤碼中的一種(完整的狀態代碼列表請參閱 參考資料 中的連結)。表示所訪問資料受到保護或者禁止訪問的 403 和 401 也很常見。無論哪種情況,這些錯誤碼都是從完成的響應 得到的。換句話說,伺服器履行了請求(即 HTTP 就緒狀態是 4)但是沒有返回客戶機預期的資料。

  因此除了就緒狀態外,還需要檢查 HTTP 狀態。我們期望的狀態代碼是 200,它表示一切順利。如果就緒狀態是 4 而且狀態代碼是 200,就可以處理伺服器的資料了,而且這些資料應該就是要求的資料(而不是錯誤或者其他有問題的資訊)。因此還要在回調方法中增加狀態檢查,如 清單 14 所示。

  清單 14. 檢查 HTTP 狀態代碼

   function updatePage() {
     if (request.readyState == 4)
       if (request.status == 200)
         alert("Server is done!");
   }

  為了增加更健壯的錯誤處理並盡量避免過於複雜,可以增加一兩個狀態代碼檢查,請看一看 清單 15 中修改後的 updatePage() 版本。

  清單 15. 增加一點錯誤檢查

   function updatePage() {
     if (request.readyState == 4)
       if (request.status == 200)
         alert("Server is done!");
       else if (request.status == 404)
         alert("Request URL does not exist");
       else
         alert("Error: status code is " + request.status);
   }

  現在將 getCustomerInfo() 中的 URL 改為不存在的 URL 看看會發生什麼。應該會看到警告資訊說明要求的 URL 不存在 —— 好極了!很難處理所有的錯誤條件,但是這一小小的改變能夠涵蓋典型 Web 應用程式中 80% 的問題。

  讀取響應文本

  現在可以確保請求已經處理完成(通過就緒狀態),伺服器給出了正常的響應(通過狀態代碼),最後我們可以處理伺服器返回的資料了。返回的資料儲存在 XMLHttpRequest 對象的 responseText 屬性中。

  關於 responseText 中的常值內容,比如格式和長度,有意保持含糊。這樣伺服器就可以將文本設定成任何內容。比方說,一種指令碼可能返回逗號分隔的值,另一種則使用管道符(即 | 字元)分隔的值,還有一種則返回長文本字串。何去何從由伺服器決定。

  在本文使用的例子中,伺服器返回客戶的上一個訂單和客戶地址,中間用管道符分開。然後使用訂單和地址設定表單中的元素值,清單 16 給出了更新顯示內容的代碼。

  清單 16. 處理伺服器響應

   function updatePage() {
     if (request.readyState == 4) {
       if (request.status == 200) {
         var response = request.responseText.split("|");
         document.getElementById("order").value = response[0];
         document.getElementById("address").innerHTML =
           response[1].replace(/\n/g, "
");
       } else
         alert("status is " + request.status);
     }
   }

  首先,得到 responseText 並使用 JavaScript split() 方法從管道符分開。得到的數組放到 response 中。數組中的第一個值 —— 上一個訂單 —— 用 response[0] 訪問,被設定為 ID 為 “order” 的欄位的值。第二個值 response[1],即客戶地址,則需要更多一點處理。因為地址中的行用一般的行分隔字元(“\n”字元)分隔,代碼中需要用 XHTML 風格的行分隔字元來代替。替換過程使用 replace() 函數和Regex完成。最後,修改後的文本作為 HTML 表單 div 中的內部 HTML。結果就是表單突然用客戶資訊更新了,如圖 4 所示。

  圖 4. 收到客戶資料後的 Break Neck 表單

  結束本文之前,我還要介紹 XMLHttpRequest 的另一個重要屬性 responseXML。如果伺服器選擇使用 XML 響應則該屬性包含(也許您已經猜到)XML 響應。處理 XML 響應和處理普通文本有很大不同,涉及到解析、文件物件模型(DOM)和其他一些問題。後面的文章中將進一步介紹 XML。但是因為 responseXML 通常和 responseText 一起討論,這裡有必要提一提。對於很多簡單的 Ajax 應用程式 responseText 就夠了,但是您很快就會看到通過 Ajax 應用程式也能很好地處理 XML。

  結束語

  您可能對 XMLHttpRequest 感到有點厭倦了,我很少看到一整篇文章討論一個對象,特別是這種簡單的對象。但是您將在使用 Ajax 編寫的每個頁面和應用程式中反覆使用該對象。坦白地說,關於 XMLHttpRequest 還真有一些可說的內容。下一期文章中將介紹如何在請求中使用 POST 及 GET,來佈建要求中的內容標題部和從伺服器響應讀取內容標題部,理解如何在請求/響應模型中編碼請求和處理 XML。

  再往後我們將介紹常見 Ajax 工具箱。這些工具箱實際上隱藏了本文所述的很多細節,使得 Ajax 編程更容易。您也許會想,既然有這麼多工具箱為何還要對底層的細節編碼。答案是,如果不知道應用程式在做什麼,就很難發現應用程式中的問題。

  因此不要忽略這些細節或者簡單地瀏覽一下,如果便捷華麗的工具箱出現了錯誤,您就不必撓頭或者發送郵件請求支援了。如果瞭解如何直接使用 XMLHttpRequest,就會發現很容易調試和解決最奇怪的問題。只有讓其解決您的問題,工具箱才是好東西。

  因此請熟悉 XMLHttpRequest 吧。事實上,如果您有使用工具箱的 Ajax 代碼,可以嘗試使用 XMLHttpRequest 對象及其屬性和方法重新改寫。這是一種不錯的練習,可以協助您更好地理解其中的原理。



相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.