掌握 Ajax,第 2 部分: 使用 JavaScript 和 Ajax 發出異步請求

轉http://www.ibm.com/developerworks/cn/xml/wa-ajaxintro2/

掌握 Ajax,第 2 部分: 使用 JavaScript 和 Ajax 發出異步請求

在 Web 請求中使用 XMLHttpRequest

多數 Web 應用程序都使用請求/響應模型從服務器上獲得完整的 HTML 頁面。常常是點擊一個按鈕,等待服務器響應,再點擊另一個按鈕,然后再等待,這樣一個反復的過程。有了 Ajax 和 XMLHttpRequest 對象,就可以使用不必讓用戶等待服務器響應的請求/響應模型了。本文中,Brett McLaughlin 介紹了如何創建能夠適應不同瀏覽器的 XMLHttpRequest 實例,建立和發送請求,并響應服務器。

Brett McLaughlin?(brett@newInstance.com), 作家,編輯, O'Reilly Media Inc.

2006 年 2 月 16 日

  • expand內容

本系列的上一期文章(請參閱?參考資料?中的鏈接),我們介紹了 Ajax 應用程序,考察了推動 Ajax 應用程序的基本概念。其中的核心是很多您可能已經了解的技術:JavaScript、HTML 和 XHTML、一點動態 HTML 以及 DOM(文檔對象模型)。本文將放大其中的一點,把目光放到具體的 Ajax 細節上。

本文中,您將開始接觸最基本和基礎性的有關 Ajax 的全部對象和編程方法:XMLHttpRequest?對象。該對象實際上僅僅是一個跨越所有 Ajax 應用程序的公共線程,您可能已經預料到,只有徹底理解該對象才能充分發揮編程的潛力。事實上,有時您會發現,要正確地使用?XMLHttpRequest,顯然不能?使用XMLHttpRequest。這到底是怎么回事呢?

Web 2.0 一瞥

在深入研究代碼之前首先看看最近的觀點 —— 一定要十分清楚 Web 2.0 這個概念。聽到 Web 2.0 這個詞的時候,應該首先問一問 “Web 1.0 是什么?” 雖然很少聽人提到 Web 1.0,實際上它指的就是具有完全不同的請求和響應模型的傳統 Web。比如,到 Amazon.com 網站上點擊一個按鈕或者輸入搜索項。就會對服務器發送一個請求,然后響應再返回到瀏覽器。該請求不僅僅是圖書和書目列表,而是另一個完整的 HTML 頁面。因此當 Web 瀏覽器用新的 HTML 頁面重繪時,可能會看到閃爍或抖動。事實上,通過看到的每個新頁面可以清晰地看到請求和響應。

Web 2.0(在很大程度上)消除了這種看得見的往復交互。比如訪問 Google Maps 或 Flickr 這樣的站點(到這些支持 Web 2.0 和 Ajax 站點的鏈接請參閱?參考資料)。比如在 Google Maps 上,您可以拖動地圖,放大和縮小,只有很少的重繪操作。當然這里仍然有請求和響應,只不過都藏到了幕后。作為用戶,體驗更加舒適,感覺很像桌面應用程序。這種新的感受和范型就是當有人提到 Web 2.0 時您所體會到的。

需要關心的是如何使這些新的交互成為可能。顯然,仍然需要發出請求和接收響應,但正是針對每次請求/響應交互的 HTML 重繪造成了緩慢、笨拙的 Web 交互的感受。因此很清楚,我們需要一種方法使發送的請求和接收的響應?包含需要的數據而不是整個 HTML 頁面。惟一需要獲得整個新 HTML 頁面的時候就是希望用戶看到?新頁面的時候。

但多數交互都是在已有頁面上增加細節、修改主體文本或者覆蓋原有數據。這些情況下,Ajax 和 Web 2.0 方法允許在?更新整個 HTML 頁面的情況下發送和接收數據。對于那些經常上網的人,這種能力可以讓您的應用程序感覺更快、響應更及時,讓他們不時地光顧您的網站。

XMLHttpRequest 簡介

要真正實現這種絢麗的奇跡,必須非常熟悉一個 JavaScript 對象,即?XMLHttpRequest。這個小小的對象實際上已經在幾種瀏覽器中存在一段時間了,它是本專欄今后幾個月中要介紹的 Web 2.0、Ajax 和大部分其他內容的核心。為了讓您快速地大體了解它,下面給出將要用于該對象的很少的幾個?方法和屬性。

  • open():建立到服務器的新請求。
  • send():向服務器發送請求。
  • abort():退出當前請求。
  • readyState:提供當前 HTML 的就緒狀態。
  • responseText:服務器返回的請求響應文本。

如果不了解這些(或者其中的任何?一個),您也不用擔心,后面幾篇文章中我們將介紹每個方法和屬性。現在應該?了解的是,明確用XMLHttpRequest?做什么。要注意這些方法和屬性都與發送請求及處理響應有關。事實上,如果看到?XMLHttpRequest?的所有方法和屬性,就會發現它們?與非常簡單的請求/響應模型有關。顯然,我們不會遇到特別新的 GUI 對象或者創建用戶交互的某種超極神秘的方法,我們將使用非常簡單的請求和非常簡單的響應。聽起來似乎沒有多少吸引力,但是用好該對象可以徹底改變您的應用程序。

簡單的 new

首先需要創建一個新變量并賦給它一個?XMLHttpRequest?對象實例。這在 JavaScript 中很簡單,只要對該對象名使用?new?關鍵字即可,如?清單 1?所示。

清單 1. 創建新的 XMLHttpRequest 對象
<script language="javascript" type="text/javascript">
var request = new XMLHttpRequest();
</script>

不難吧?記住,JavaScript 不要求指定變量類型,因此不需要像?清單 2?那樣做(在 Java 語言中可能需要這樣)。

清單 2. 創建 XMLHttpRequest 的 Java 偽代碼
XMLHttpRequest request = new XMLHttpRequest();

因此在 JavaScript 中用?var?創建一個變量,給它一個名字(如 “request”),然后賦給它一個新的?XMLHttpRequest?實例。此后就可以在函數中使用該對象了。

錯誤處理

在實際上各種事情都可能出錯,而上面的代碼沒有提供任何錯誤處理。較好的辦法是創建該對象,并在出現問題時優雅地退出。比如,任何較早的瀏覽器(不論您是否相信,仍然有人在使用老版本的 Netscape Navigator)都不支持?XMLHttpRequest,您需要讓這些用戶知道有些地方出了問題。清單 3?說明如何創建該對象,以便在出現問題的時候發出 JavaScript 警告。

清單 3. 創建具有錯誤處理能力的 XMLHttpRequest
<script language="javascript" type="text/javascript">
var request = false;
try {request = new XMLHttpRequest();
} catch (failed) {request = false;
}
if (!request)alert("Error initializing XMLHttpRequest!");
</script>

一定要理解這些步驟:

  1. 創建一個新變量?request?并賦值 false。后面將使用 false 作為判定條件,它表示還沒有創建?XMLHttpRequest?對象。
  2. 增加 try/catch 塊:
    1. 嘗試創建?XMLHttpRequest?對象。
    2. 如果失敗(catch (failed))則保證?request?的值仍然為 false。
  3. 檢查?request?是否仍為 false(如果一切正常就不會是 false)。
  4. 如果出現問題(request?是 false)則使用 JavaScript 警告通知用戶出現了問題。

代碼非常簡單,對大多數 JavaScript 和 Web 開發人員來說,真正理解它要比讀寫代碼花更長的時間。現在已經得到了一段帶有錯誤檢查的XMLHttpRequest?對象創建代碼,還可以告訴您哪兒出了問題。

應付 Microsoft

看起來似乎一切良好,至少在用 Internet Explorer 試驗這些代碼之前是這樣的。如果這樣試驗的話,就會看到?圖 1?所示的糟糕情形。

圖 1. Internet Explorer 報告錯誤
Internet Explorer 報告錯誤

Microsoft 參與了嗎?

關于 Ajax 和 Microsoft 對該領域不斷增長的興趣和參與已經有很多文章進行了介紹。事實上,據說 Microsoft 最新版本的 Internet Explorer —— version 7.0,將在 2006 年下半年推出 —— 將開始直接支持?XMLHttpRequest,讓您使用?new?關鍵字代替所有的?Msxml2.XMLHTTP?創建代碼。但不要太激動,仍然需要支持舊的瀏覽器,因此跨瀏覽器代碼不會很快消失。

顯然有什么地方不對勁,而 Internet Explorer 很難說是一種過時的瀏覽器,因為全世界有 70% 在使用 Internet Explorer。換句話說,如果不支持 Microsoft 和 Internet Explorer 就不會受到 Web 世界的歡迎!因此我們需要采用不同的方法處理 Microsoft 瀏覽器。

經驗證發現 Microsoft 支持 Ajax,但是其?XMLHttpRequest?版本有不同的稱呼。事實上,它將其稱為幾種?不同的東西。如果使用較新版本的 Internet Explorer,則需要使用對象?Msxml2.XMLHTTP,而較老版本的 Internet Explorer 則使用?Microsoft.XMLHTTP。我們需要支持這兩種對象類型(同時還要支持非 Microsoft 瀏覽器)。請看看?清單 4,它在前述代碼的基礎上增加了對 Microsoft 的支持。

清單 4. 增加對 Microsoft 瀏覽器的支持
<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!");
</script>

很容易被這些花括號迷住了眼睛,因此下面分別介紹每一步:

  1. 創建一個新變量?request?并賦值 false。使用 false 作為判斷條件,它表示還沒有創建?XMLHttpRequest?對象。
  2. 增加 try/catch 塊:
    1. 嘗試創建?XMLHttpRequest?對象。
    2. 如果失敗(catch (trymicrosoft)):
      1. 嘗試使用較新版本的 Microsoft 瀏覽器創建 Microsoft 兼容的對象(Msxml2.XMLHTTP)。
      2. 如果失敗(catch (othermicrosoft))嘗試使用較老版本的 Microsoft 瀏覽器創建 Microsoft 兼容的對象(Microsoft.XMLHTTP)。
    3. 如果失敗(catch (failed))則保證?request?的值仍然為 false。
  3. 檢查?request?是否仍然為 false(如果一切順利就不會是 false)。
  4. 如果出現問題(request?是 false)則使用 JavaScript 警告通知用戶出現了問題。

這樣修改代碼之后再使用 Internet Explorer 試驗,就應該看到已經創建的表單(沒有錯誤消息)。我實驗的結果如?圖 2?所示。

圖 2. Internet Explorer 正常工作
Internet Explorer 正常工作

靜態與動態

再看一看清單?1、3?和?4,注意,所有這些代碼都直接嵌套在?script?標記中。像這種不放到方法或函數體中的 JavaScript 代碼稱為靜態 JavaScript。就是說代碼是在頁面顯示給用戶之前的某個時候運行。(雖然根據規范不能完全精確地?知道這些代碼何時運行對瀏覽器有什么影響,但是可以保證這些代碼在用戶能夠與頁面交互之前運行。)這也是多數 Ajax 程序員創建?XMLHttpRequest?對象的一般方式。

就是說,也可以像?清單 5?那樣將這些代碼放在一個方法中。

清單 5. 將 XMLHttpRequest 創建代碼移動到方法中
<script language="javascript" type="text/javascript">
var request;
function createRequest() {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!");
}
</script>

如果按照這種方式編寫代碼,那么在處理 Ajax 之前需要調用該方法。因此還需要?清單 6?這樣的代碼。

清單 6. 使用 XMLHttpRequest 的創建方法
<script language="javascript" type="text/javascript">
var request;
function createRequest() {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() {createRequest();// Do something with the request variable
}
</script>

此代碼惟一的問題是推遲了錯誤通知,這也是多數 Ajax 程序員不采用這一方法的原因。假設一個復雜的表單有 10 或 15 個字段、選擇框等,當用戶在第 14 個字段(按照表單順序從上到下)輸入文本時要激活某些 Ajax 代碼。這時候運行?getCustomerInfo()?嘗試創建一個XMLHttpRequest?對象,但(對于本例來說)失敗了。然后向用戶顯示一條警告,明確地告訴他們不能使用該應用程序。但用戶已經花費了很多時間在表單中輸入數據!這是非常令人討厭的,而討厭顯然不會吸引用戶再次訪問您的網站。

如果使用靜態 JavaScript,用戶在點擊頁面的時候很快就會看到錯誤信息。這樣也很煩人,是不是?可能令用戶錯誤地認為您的 Web 應用程序不能在他的瀏覽器上運行。不過,當然要比他們花費了 10 分鐘輸入信息之后再顯示同樣的錯誤要好。因此,我建議編寫靜態的代碼,讓用戶盡可能早地發現問題。

用 XMLHttpRequest 發送請求

得到請求對象之后就可以進入請求/響應循環了。記住,XMLHttpRequest?惟一的目的是讓您發送請求和接收響應。其他一切都是 JavaScript、CSS 或頁面中其他代碼的工作:改變用戶界面、切換圖像、解釋服務器返回的數據。準備好?XMLHttpRequest?之后,就可以向服務器發送請求了。

歡迎使用沙箱

Ajax 采用一種沙箱安全模型。因此,Ajax 代碼(具體來說就是?XMLHttpRequest?對象)只能對所在的同一個域發送請求。以后的文章中將進一步介紹安全和 Ajax,現在只要知道在本地機器上運行的代碼只能對本地機器上的服務器端腳本發送請求。如果讓 Ajax 代碼在 www.breakneckpizza.com 上運行,則必須 www.breakneck.com 中運行的腳本發送請求。

設置服務器 URL

首先要確定連接的服務器的 URL。這并不是 Ajax 的特殊要求,但仍然是建立連接所必需的,顯然現在您應該知道如何構造 URL 了。多數應用程序中都會結合一些靜態數據和用戶處理的表單中的數據來構造該 URL。比如,清單 7?中的 JavaScript 代碼獲取電話號碼字段的值并用其構造 URL。

清單 7. 建立請求 URL
<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);}
</script>

這里沒有難懂的地方。首先,代碼創建了一個新變量?phone,并把 ID 為 “phone” 的表單字段的值賦給它。清單 8?展示了這個表單的 XHTML,其中可以看到?phone?字段及其?id?屬性。

清單 8. Break Neck Pizza 表單
 <body><p><img src="breakneck-logo_4c.gif" alt="Break Neck Pizza" /></p><form action="POST"><p>Enter your phone number:<input type="text" size="14" name="phone" id="phone" onChange="getCustomerInfo();" /></p><p>Your order will be delivered to:</p><div id="address"></div><p>Type your order in here:</p><p><textarea name="order" rows="6" cols="50" id="order"></textarea></p><p><input type="submit" value="Order Pizza" id="submit" /></p></form></body>

還要注意,當用戶輸入電話號碼或者改變電話號碼時,將觸發?清單 8?所示的?getCustomerInfo()?方法。該方法取得電話號碼并構造存儲在url?變量中的 URL 字符串。記住,由于 Ajax 代碼是沙箱型的,因而只能連接到同一個域,實際上 URL 中不需要域名。該例中的腳本名為/cgi-local/lookupCustomer.php。最后,電話號碼作為 GET 參數附加到該腳本中:"phone=" + escape(phone)

如果以前沒用見過?escape()?方法,它用于轉義不能用明文正確發送的任何字符。比如,電話號碼中的空格將被轉換成字符?%20,從而能夠在 URL 中傳遞這些字符。

可以根據需要添加任意多個參數。比如,如果需要增加另一個參數,只需要將其附加到 URL 中并用 “與”(&)字符分開 [第一個參數用問號(?)和腳本名分開]。

打開請求

open() 是打開嗎?

Internet 開發人員對?open()?方法到底做什么沒有達成一致。但它實際上并不是?打開一個請求。如果監控 XHTML/Ajax 頁面及其連接腳本之間的網絡和數據傳遞,當調用?open()?方法時將看不到任何通信。不清楚為何選用了這個名字,但顯然不是一個好的選擇。

有了要連接的 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?發送給它的請求處理之后需要某種指示說明怎么做。

在 JavaScript 中引用函數

JavaScript 是一種弱類型的語言,可以用變量引用任何東西。因此如果聲明了一個函數?updatePage(),JavaScript 也將該函數名看作是一個變量。換句話說,可用變量名?updatePage?在代碼中引用函數。

現在?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 代碼
彈出警告的 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");elsealert("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, "
");
} elsealert("status is " + request.status);}}

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

圖 4. 收到客戶數據后的 Break Neck 表單
收到客戶數據后的 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?對象及其屬性和方法重新改寫。這是一種不錯的練習,可以幫助您更好地理解其中的原理。

下一期文章中將進一步討論該對象,探討它的一些更有趣的屬性(如?responseXML),以及如何使用?POST?請求和以不同的格式發送數據。請開始編寫代碼吧,一個月后我們再繼續討論。

參考資料

學習

  • 您可以參閱本文在 developerWorks 全球站點上的?英文原文。
  • 掌握 Ajax,第 1 部分: Ajax 簡介(developerWorks,2005 年 12 月)幫助您了解 Ajax,這是一種構建網站的高生產率方法。(本文中的參考資料列表都值得訪問!)
  • 面向 Java 開發人員的 Ajax: 構建動態的 Java 應用程序(developerWorks,2005 年 9 月)從 Java 的角度考察了 Ajax 服務器端。
  • 面向 Java 開發人員的 Ajax: Ajax 的 Java 對象序列化(developerWorks,2005 年 10 月)從 Java 的角度分析了如何通過網絡發送對象以及與 Ajax 交互。
  • 使用 AJAX 調用 SOAP Web 服務,第 1 部分: 構建 Web 服務客戶機(developerWorks,2005 年 10 月)是關于集成 Ajax 和現有基于 SOAP 的 web 服務的相當高級的文章。
  • Google GMail?是一個很好的例子,說明了基于 Ajax 的應用程序如何改變 Web 的工作方式。
  • Google Maps?是另一種基于 Google 的 Web 2.0 應用程序。
  • Flickr?是一個很好的例子,說明如何使用 Ajax 創建類似桌面的 Web 應用程序。
  • Ajax: A New Approach to Web Applications?發明了 Ajax 一詞,所有 Ajax 開發人員都應該讀一讀。
  • Why Ajax Matters Now?告訴您為什么 Ajax 很重要。
  • 如果使用的是 Microsoft 瀏覽器 Internet Explorer,請訪問?Microsoft Developer Network's XML Developer Center。
  • 請通過?在線文檔?進一步了解 MSXML,Microsoft XML 解析器。
  • 看一看響應中包含的所有?HTTP 狀態碼?列表。
  • developerWorks?Web Architecture 專區?專門發表各種基于 Web 的解決方案的文章。

獲得產品和技術

  • Elisabeth Freeman、Eric Freeman 和 Brett McLaughlin 合著的?Head Rush Ajax(2005 年 2 月,O'Reilly Media, Inc.)以 Head First 風格將本文中所述的內容灌輸到您的頭腦中。
  • Java and XML, Second Edition(Brett McLaughlin,2001 年 8 月,O'Reilly Media, Inc.)包括作者關于 XHTML 和 XML 轉換的討論。
  • JavaScript: The Definitive Guide(David Flanagan,2001 年 11 月,O'Reilly Media, Inc.)詳細介紹了如何使用 JavaScript、動態網頁,第二版增加了關于 Ajax 的兩章。
  • Head First HTML with CSS & XHTML(Elizabeth 和Eric Freeman,2005 年 12 月,O'Reilly Media, Inc.)是學習 XHTML、CSS 以及如何將兩者結合起來的完整參考。

討論

  • 參與論壇討論。
  • developerWorks blogs:加入 developerWorks 社區。

轉載于:https://www.cnblogs.com/anruy/p/5038467.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/458571.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/458571.shtml
英文地址,請注明出處:http://en.pswp.cn/news/458571.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Provisioning Services 7.8 入門系列教程之十一 通過版本控制自動更新虛擬磁盤

續Provisioning Services 7.8 入門系列教程之十 通過類自動更新虛擬磁盤從前兩的兩種更新方式可以看出&#xff0c;它們有一個共同的特點&#xff0c;即需要產生&#xff08;復制&#xff09;完成的虛擬磁盤副本&#xff0c;然后進行相關的升級操作。這兩種方法在實際生產中&am…

OC面試題

什么是KVC和KVO&#xff1f; 答&#xff1a;KVC(Key-Value-Coding)內部的實現&#xff1a;一個對象在調用setValue的時候&#xff0c; &#xff08;1&#xff09;首先根據方法名找到運行方法的時候所需要的環境參數。 &#xff08;2&#xff09;他會從自己isa指針結合環境參數&…

【算法】QuickSort

快速排序&#xff0c;時間復雜度O(N*logN)&#xff0c;要能熟練掌握&#xff01; 以下主要參考http://blog.csdn.net/morewindows/article/details/6684558&#xff0c; 感謝原博主&#xff01; 該方法的基本思想是&#xff1a; 1&#xff0e;先從數列中取出一個數作為基準數。…

串口之GetCommState、SetCommState函數詳解

GetCommState 讀取串口設置(波特率,校驗,停止位,數據位等).函數聲明&#xff1a;BOOL GetCommState(HANDLE hFile,LPDCB lpDCB);GetCommState函數的第一個參數hFile是由CreateFile函數返回指向已打開串行口的句柄。第二個參數指向設備控制塊DCB。如果函數調用成功&#xff0c;則…

登錄失敗時記住訪問的地址

登錄失敗時記住訪問的地址 使用spring MVC 訪問時,在攔截器中記錄訪問的地址: Java代碼 String path request.getRequestURI();//"/demo_channel_terminal/news/list" System.out.println("您無權訪問:" path); //用于登錄成功…

串口之GetCommTimeouts、SetCommTimeouts函數詳解

Windows系統利用此函數獲取特定的通訊設備讀寫時的超時參數設定&#xff0c;GetCommTimeouts函數聲明如下&#xff1a;BOOL GetCommTimeouts(HANDLE hFile,LPCOMMTIMEOUTS lpCommTimeouts);GetCommTimeouts函數的第一個參數hFile是由CreateFile函數返回指向已打開串行口的句柄。…

GUN/LINUX命令之 cp mv install

1. cp命令 復制copy命令的簡寫 SYNOPSIS cp [OPTION]... [-T] SOURCE DEST cp [OPTION]... SOURCE... DIRECTORY cp [OPTION]... -t DIRECTORY SOURCE... cp SOURCE DEST 后者如果是目錄那么源文件就復制到文件夾里面并且保持著原來的名字&#xff1b;如果D…

Tomcat - Maven plugin: 運行找不到webapp

2019獨角獸企業重金招聘Python工程師標準>>> The tomcat7-maven-plugin allows running the current project as a Web application and additional <webapps> can be specified that will be simultaneously loaded into tomcat. My project is not a Web ap…

面試題3

1. 你如何理解 iOS 內存管理 1. new alloc copy retain這些對象我們都要主動的release或者 autorelease 2. 如果是類方法創建的對象,那么系統自動釋放池自動在適當的 時候會幫我們 release 3. ARC xcode 自動會幫我們人工智能的添加 release autorelease 操 作 2. C語言里的數…

基于MQTT協議進行應用開發

來自&#xff1a;http://www.cnblogs.com/secondtononewe/p/6073089.html 官方協議有句如下的話來形容MQTT的設計思想&#xff1a; “It is designed for connections with remote locations where a "small code footprint" is required or the network bandwidth i…

SortedDictionaryTKey,TValue正序與反序排序及Dicttionary相關

SortedDictionary<TKey,TValue>能對字典排序 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace SortDictionary {class Program{static void Main(string[] args){TestDictionarySort();…

DOS窗口的編碼頁從UTF-8調回GBK

2019獨角獸企業重金招聘Python工程師標準>>> 之前在DOS窗口操作MySQL數據庫的時候&#xff0c;將編碼頁從GBK設置成了UTF-8&#xff0c;解決了在DOS窗口顯示MySQL數據庫中的表中的中文字符出現亂碼的問題。但是除此之外&#xff0c;DOS窗口顯示的其他中文字符都是亂…

UIBezierPath

學習UIBezierPath畫圖 筆者在寫本篇文章之前&#xff0c;也沒有系統學習過貝塞爾曲線&#xff0c;只是曾經某一次的需求需要使用到&#xff0c;才臨時百度看了一看而且使用最基本的功能。現在總算有時間停下來好好研究研究這個神奇而偉大的貝塞爾先生&#xff01; 筆者在學習時…

系統架構設計理論與原則

一、無共享架構 1、無共享架構 無共享架構是一種分布式計算架構&#xff0c;這種架構中不存在集中存儲的狀態&#xff0c;系統中每個節點都是獨立自治的&#xff0c;整個系統中沒有資源競爭&#xff0c;這種架構具有非常強的擴張性&#xff0c;目前在web應用中被廣泛使用。 無共…

VS2010 教程:創建一個 WPF 應用程序 (第一節)

來自&#xff1a;https://msdn.microsoft.com/zh-cn/library/ff629048.aspx [原文發表地址] VS2010 Tutorial: Build a WPF App (Step 1) [原文發表時間] Friday, May 22, 2009 8:00 AM 這篇文章里&#xff0c;我將使用VS2010 Beta 1創建一個WPF 應用程序。并且 我將展示這個產…

js 日期星期 帶農歷

Weekday代碼 //得到當前日期如2009年6月19日 星期五 function getDate(){ var today new Date(); var x new Array("星期日", "星期一", "星期二","星期三","星期四", "星期五","星期六"…

FMDB的使用

// // FMDBmanager.h // database // // Created by PRL on 16/10/13. // Copyright © 2016年PRL. All rights reserved. // #import <Foundation/Foundation.h> interface FMDBmanager : NSObject{ FMDatabase * _db; } (FMDBmanager *)sharedManager; //獲取…

深入淺出WPF之Binding的使用(一)

from: http://www.cnblogs.com/akwwl/p/3421005.html 在WPF中Binding可以比作數據的橋梁&#xff0c;橋梁的兩端分別是Binding的源&#xff08;Source&#xff09;和目標&#xff08;Target&#xff09;。 一般情況下&#xff0c;Binding源是邏輯層對象&#xff0c;Binding目…

arm處理器中a5 a8 a9,v6 v7,arm7 arm9 arm11都是依據什么來分類的【轉】

轉自&#xff1a;http://blog.csdn.net/maochengtao/article/details/9951131ARM處理器發展這么多年&#xff0c;有很多架構&#xff0c;很多不同的內核 架構有armv1 v2 v3 v4 v5 v6 v7 內核太多了&#xff0c;比如armv1對應的是arm1&#xff0c;armv5對應的arm9&#xff0c;ar…

前端開發一些很有用的工具

apiview.com 接口規范管理平臺 restClient 谷歌瀏覽器接口測試工具 postman 接口測試工具 SSH Secure Shell Client 抓包工具 SSH SecureFile Transfer Client wireshark 抓包分析工具 Xshell linux遠程工具 Balsamiq Mockups 原型圖 visio 流程圖 xmind top圖 SourceCounter、…