多數Web 應用程式都使用請求/回應模型從伺服器上獲得完整的HTML 頁面。常常是點擊一個按鈕,等待伺服器回應,再點擊另一個按鈕,然後再等待,這樣一個反覆的過程。有了Ajax 和XMLHttpRequest 對象,就可以使用不必讓使用者等待伺服器回應的請求/回應模型了。本文中,Brett McLaughlin 介紹如何建立能夠適應不同瀏覽器的XMLHttpRequest 實例,建立和傳送請求,並回應伺服器。
本系列的上一期文章(請參閱參考資料中的連結),我們介紹了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 很難說是一種過時的瀏覽器,因為全世界有70% 在使用Internet Explorer。換句話說,如果不支援Microsoft 和Internet Explorer 就不會受到Web 世界的歡迎!因此我們需要採用不同的方法來處理Microsoft 瀏覽器。
經驗證發現Microsoft 支援Ajax,但是其XMLHttpRequest 版本有不同的稱呼。事實上,它將其稱為幾種不同的東西。如果使用較新版本的Internet Explorer,則需要使用物件Msxml2.XMLHTTP,而較舊版本的Internet Explorer 則使用Microsoft.XMLHTTP。我們需要支援這兩種物件類型(同時也要支援非Microsoft 瀏覽器)。請看看清單4,它在前述程式碼的基礎上增加了對Microsoft 的支援。
Microsoft 參與了嗎?
關於Ajax 和Microsoft 對該領域不斷增長的興趣和參與已經有許多文章進行了介紹。事實上,據說Microsoft 最新版本的Internet Explorer —— version 7.0,將在2006 年下半年推出—— 將開始直接支援XMLHttpRequest,讓您使用new 關鍵字取代所有的Msxml2.XMLHTTP 建立程式碼。但不要太激動,仍然需要支援舊的瀏覽器,因此跨瀏覽器程式碼不會很快消失。
清單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)。
2)如果失敗(catch (failed))則保證request 的值仍然為false。
3.檢查request 是否仍然為false(如果一切順利就不會是false)。
4.如果出現問題(request 是false)則使用JavaScript 警告通知使用者出現了問題。
這樣修改程式碼之後再使用Internet Explorer 試驗,就應該看到已經建立的表單(沒有錯誤訊息)。我實驗的結果如圖2 所示。
圖2. 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 t</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 中並用「與」(&)字元分開[第一個參數用問號(?)和腳本名分開]。
打開請求
有了要連線的URL 後就可以設定請求了。可以用XMLHttpRequest 物件的open() 方法來完成。此方法有五個參數:
request-type:發送請求的類型。典型的值是GET 或POST,但也可以發送HEAD 請求。
url:要連接的URL。
asynch:如果希望使用非同步連線則為true,否則為false。此參數是可選的,預設為true。
username:如果需要身份驗證,則可以在此指定使用者名稱。此可選參數沒有預設值。 password:如果需要身份驗證,則可以在此指定口令。此可選參數沒有預設值。
open() 是開啟嗎?
Internet 開發人員對open() 方法到底做什麼沒有達成共識。但它實際上並不是打開一個請求。如果監控XHTML/Ajax 頁面及其連接腳本之間的網路和資料傳遞,當呼叫open() 方法時將看不到任何通訊。不清楚為何選用了這個名字,但顯然不是一個好的選擇。
通常使用其中的前三個參數。事實上,即使需要非同步連接,也應該指定第三個參數為「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 在程式碼中引用函數。
清單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 風格的行分隔符號<br /> 來取代。替換過程使用replace() 函數和正規表示式完成。最後,修改後的文字作為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 物件及其屬性和方法重新改寫。這是一個不錯的練習,可以幫助您更好地理解其中的原理。
下一期文章中將進一步討論該對象,探討它的一些更有趣的屬性(如responseXML),以及如何使用POST 請求和以不同的格式發送資料。請開始寫程式碼吧,一個月後我們再繼續討論。