使用JavaScript 和 Ajax 處理非同步請求
阿新 • • 發佈:2019-02-17
一、概述
多數 Web 應用程式都使用請求/響應模型從伺服器上獲得完整的 HTML 頁面。常常是點選一個按鈕,等待伺服器響應,再點選另一個按鈕,然後再等待,這樣一個反覆的過程。有了 Ajax 和 XMLHttpRequest 物件,就可以使用不必讓使用者等待伺服器響應的請求/響應模型了。
Web 2.0(在很大程度上)消除了這種看得見的往復互動。比如訪問 Google Maps 或 Flickr 這樣的站點。比如在 Google Maps 上,您可以拖動地圖,放大和縮小,只有很少的重繪操作。當然這裡仍然有請求和響應,只不過都藏到了幕後。作為使用者,體驗更加舒適,感覺很像桌面應用程式。這種新的感受和範型就是當有人提到 Web 2.0 時您所體會到的。
2、XMLHttpRequest 簡介
該物件幾個方法和屬性。
open():建立到伺服器的新請求。
send():向伺服器傳送請求。
abort():退出當前請求。
readyState:提供當前 HTML 的就緒狀態。
responseText:伺服器返回的請求響應文字。
3、簡單的 new
首先需要建立一個新變數並賦給它一個 XMLHttpRequest 物件例項。這在 JavaScript 中很簡單,只要對該物件名使用 new 關鍵字即可,如 清單 1 所示。
清單 1. 建立新的 XMLHttpRequest 物件
清單 2. 建立 XMLHttpRequest 的 Java 虛擬碼
XMLHttpRequest request = new XMLHttpRequest();
在 JavaScript 中用 var 建立一個變數,給它一個名字(如 “request”),然後賦給它一個新的 XMLHttpRequest 例項。此後就可以在函式中使用該物件了。
4、錯誤處理
在實際上各種事情都可能出錯,而上面的程式碼沒有提供任何錯誤處理。較好的辦法是建立該物件,並在出現問題時優雅地退出。比如,任何較早的瀏覽器(不論您是否相信,仍然有人在使用老版本的 Netscape Navigator)都不支援 XMLHttpRequest,您需要讓這些使用者知道有些地方出了問題。清單 3 說明如何建立該物件,以便在出現問題的時候發出 JavaScript 警告。
清單 3. 建立具有錯誤處理能力的 XMLHttpRequest
建立一個新變數 request 並賦值 false。後面將使用 false 作為判定條件,它表示還沒有建立 XMLHttpRequest 物件。
增加 try/catch 塊:
1)嘗試建立 XMLHttpRequest 物件。
2)如果失敗(catch (failed))則保證 request 的值仍然為 false。
檢查 request 是否仍為 false(如果一切正常就不會是 false)。
如果出現問題(request 是 false)則使用 JavaScript 警告通知使用者出現了問題。
程式碼非常簡單,對大多數 JavaScript 和 Web 開發人員來說,真正理解它要比讀寫程式碼花更長的時間。現在已經得到了一段帶有錯誤檢查的 XMLHttpRequest 物件建立程式碼,還可以告訴您哪兒出了問題。
5、應付 Microsoft
清單 4. 增加對 Microsoft 瀏覽器的支援
建立一個新變數 request 並賦值 false。使用 false 作為判斷條件,它表示還沒有建立 XMLHttpRequest 物件。
增加 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 試驗,就應該看到已經建立的表單(沒有錯誤訊息)。
6、靜態與動態
再看一看清單 1、3 和 4,注意,所有這些程式碼都直接巢狀在 script 標記中。像這種不放到方法或函式體中的 JavaScript 程式碼稱為靜態 JavaScript。就是說程式碼是在頁面顯示給使用者之前的某個時候執行。(雖然根據規範不能完全精確地 知道這些程式碼何時執行對瀏覽器有什麼影響,但是可以保證這些程式碼在使用者能夠與頁面互動之前執行。)這也是多數 Ajax 程式設計師建立 XMLHttpRequest 物件的一般方式。
就是說,也可以像 清單 5 那樣將這些程式碼放在一個方法中。
清單 5. 將 XMLHttpRequest 建立程式碼移動到方法中
清單 6. 使用 XMLHttpRequest 的建立方法
如果使用靜態 JavaScript,使用者在點選頁面的時候很快就會看到錯誤資訊。這樣也很煩人,是不是?可能令使用者錯誤地認為您的 Web 應用程式不能在他的瀏覽器上執行。不過,當然要比他們花費了 10 分鐘輸入資訊之後再顯示同樣的錯誤要好。因此,我建議編寫靜態的程式碼,讓使用者儘可能早地發現問題。
7、用 XMLHttpRequest 傳送請求
得到請求物件之後就可以進入請求/響應迴圈了。記住,XMLHttpRequest 惟一的目的是讓您傳送請求和接收響應。其他一切都是 JavaScript、CSS 或頁面中其他程式碼的工作:改變使用者介面、切換影象、解釋伺服器返回的資料。準備好 XMLHttpRequest 之後,就可以向伺服器傳送請求了。
Ajax 採用一種沙箱安全模型。因此,Ajax 程式碼(具體來說就是 XMLHttpRequest 物件)只能對所在的同一個域傳送請求。在本地機器上執行的程式碼只能對本地機器上的伺服器端指令碼傳送請求。如果讓 Ajax 程式碼在 http://www.breakneckpizza.com/ 上執行,則必須向 http://www.breakneck.com/ 中執行的指令碼傳送請求。
8、設定伺服器 URL
首先要確定連線的伺服器的 URL。這並不是 Ajax 的特殊要求,但仍然是建立連線所必需的。多數應用程式中都會結合一些靜態資料和使用者處理的表單中的資料來構造該 URL。比如,清單 7 中的 JavaScript 程式碼獲取電話號碼欄位的值並用其構造 URL。
清單 7. 建立請求 URL
清單 8. Break Neck Pizza 表單
如果以前沒用見過 escape() 方法,它用於轉義不能用明文正確傳送的任何字元。比如,電話號碼中的空格將被轉換成字元 %20,從而能夠在 URL 中傳遞這些字元。
可以根據需要新增任意多個引數。比如,如果需要增加另一個引數,只需要將其附加到 URL 中並用 “與”(&)字元分開 [第一個引數用問號(?)和指令碼名分開]。
9、開啟請求
有了要連線的 URL 後就可以配置請求了。可以用 XMLHttpRequest 物件的 open() 方法來完成。該方法有五個引數:
request-type:傳送請求的型別。典型的值是 GET 或 POST,但也可以傳送 HEAD 請求。
url:要連線的 URL。
asynch:如果希望使用非同步連線則為 true,否則為 false。該引數是可選的,預設為 true。
username:如果需要身份驗證,則可以在此指定使用者名稱。該可選引數沒有預設值。 password:如果需要身份驗證,則可以在此指定口令。該可選引數沒有預設值。
Internet 開發人員對 open() 方法到底做什麼沒有達成一致。但它實際上並不是 開啟一個請求。如果監控 XHTML/Ajax 頁面及其連線指令碼之間的網路和資料傳遞,當呼叫 open() 方法時將看不到任何通訊。
通常使用其中的前三個引數。事實上,即使需要非同步連線,也應該指定第三個引數為 “true”。這是預設值,但堅持明確指定請求是非同步的還是同步的更容易理解。
將這些結合起來,通常會得到 清單 9 所示的一行程式碼。
清單 9. 開啟請求
10、非同步性
在一般的請求/響應模型中,比如 Web 1.0,客戶機(瀏覽器或者本地機器上執行的程式碼)向伺服器發出請求。該請求是同步的,換句話說,客戶機等待伺服器的響應。當客戶機等待的時候,至少會用某種形式通知您在等待:
沙漏(特別是 Windows 上)。
旋轉的皮球(通常在 Mac 機器上)。
應用程式基本上凍結了,然後過一段時間游標變化了。
這正是 Web 應用程式讓人感到笨拙或緩慢的原因 —— 缺乏真正的互動性。按下按鈕時,應用程式實際上變得不能使用,直到剛剛觸發的請求得到響應。如果請求需要大量伺服器處理,那麼等待的時間可能很長(至少在這個多處理器、DSL 沒有等待的世界中是如此)。
而非同步請求不 等待伺服器響應。傳送請求後應用程式繼續執行。使用者仍然可以在 Web 表單中輸入資料,甚至離開表單。沒有旋轉的皮球或者沙漏,應用程式也沒有明顯的凍結。伺服器悄悄地響應請求,完成後告訴原來的請求者工作已經結束(具體的辦法很快就會看到)。結果是,應用程式感覺不 那麼遲鈍或者緩慢,而是響應迅速、互動性強,感覺快多了。這僅僅是 Web 2.0 的一部分,但它是很重要的一部分。所有老套的 GUI 元件和 Web 設計範型都不能克服緩慢、同步的請求/響應模型。
11、傳送請求
一旦用 open() 配置好之後,就可以傳送請求了。幸運的是,傳送請求的方法的名稱要比 open() 適當,它就是 send()。
send() 只有一個引數,就是要傳送的內容。但是在考慮這個方法之前,回想一下前面已經通過 URL 本身傳送過資料了:
var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
雖然可以使用 send() 傳送資料,但也能通過 URL 本身傳送資料。事實上,GET 請求(在典型的 Ajax 應用中大約佔 80%)中,用 URL 傳送資料要容易得多。如果需要傳送安全資訊或 XML,可能要考慮使用 send() 傳送內容。如果不需要通過 send() 傳遞資料,則只要傳遞 null 作為該方法的引數即可。因此您會發現在本文中的例子中只需要這樣傳送請求(參見 清單 10)。
清單 10. 傳送請求
12、指定回撥方法
必須承認,open() 方法中 “true” 這個小小的關鍵字建立了非同步請求。但是除此之外,這些程式碼與用 Java servlet 及 JSP、PHP 或 Perl 程式設計沒有什麼兩樣。那麼 Ajax 和 Web 2.0 最大的祕密是什麼呢?祕密就在於 XMLHttpRequest 的一個簡單屬性 onreadystatechange。
首先一定要理解這些程式碼中的流程(如果需要請回顧 清單 10)。建立其請求然後發出請求。此外,因為是非同步請求,所以 JavaScript 方法(例子中的 getCustomerInfo())不會等待伺服器。因此程式碼將繼續執行,就是說,將退出該方法而把控制返回給表單。使用者可以繼續輸入資訊,應用程式不會等待伺服器。
這就提出了一個有趣的問題:伺服器完成了請求之後會發生什麼?答案是什麼也不發生,至少對現在的程式碼而言如此!顯然這樣不行,因此伺服器在完成通過 XMLHttpRequest 傳送給它的請求處理之後需要某種指示說明怎麼做。
在 JavaScript 中引用函式:
JavaScript 是一種弱型別的語言,可以用變數引用任何東西。因此如果聲明瞭一個函式 updatePage(),JavaScript 也將該函式名看作是一個變數。換句話說,可用變數名 updatePage 在程式碼中引用函式。
清單 11. 設定回撥方法
13、處理伺服器響應
傳送請求,使用者高興地使用 Web 表單(同時伺服器在處理請求),而現在伺服器完成了請求處理。伺服器檢視 onreadystatechange 屬性確定要呼叫的方法。除此以外,可以將您的應用程式看作其他應用程式一樣,無論是否非同步。換句話說,不一定要採取特殊的動作編寫響應伺服器的方法,只需要改變表單,讓使用者訪問另一個 URL 或者做響應伺服器需要的任何事情。這一節我們重點討論對伺服器的響應和一種典型的動作 —— 即時改變使用者看到的表單中的一部分。
回撥和Ajax:
現在我們已經看到如何告訴伺服器完成後應該做什麼:將 XMLHttpRequest 物件的 onreadystatechange 屬性設定為要執行的函式名。這樣,當伺服器處理完請求後就會自動呼叫該函式。也不需要擔心該函式的任何引數。我們從一個簡單的方法開始,如 清單 12 所示。
清單 12. 回撥方法的程式碼
根據瀏覽器的不同,在表單停止彈出警告之前會看到兩次、三次甚至四次警告。這是怎麼回事呢?原來我們還沒有考慮 HTTP 就緒狀態,這是請求/響應迴圈中的一個重要部分。
14、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. 檢查就緒狀態
15、HTTP 狀態碼
雖然 清單 13 中的程式碼看起來似乎不錯,但是還有一個問題 —— 如果伺服器響應請求並完成了處理但是報告了一個錯誤怎麼辦?要知道,伺服器端程式碼應該明白它是由 Ajax、JSP、普通 HTML 表單或其他型別的程式碼呼叫的,但只能使用傳統的 Web 專用方法報告資訊。而在 Web 世界中,HTTP 程式碼可以處理請求中可能發生的各種問題。
比方說,您肯定遇到過輸入了錯誤的 URL 請求而得到 404 錯誤碼的情形,它表示該頁面不存在。這僅僅是 HTTP 請求能夠收到的眾多錯誤碼中的一種。表示所訪問資料受到保護或者禁止訪問的 403 和 401 也很常見。無論哪種情況,這些錯誤碼都是從完成的響應得到的。換句話說,伺服器履行了請求(即 HTTP 就緒狀態是 4)但是沒有返回客戶機預期的資料。
因此除了就緒狀態外,還需要檢查 HTTP 狀態。我們期望的狀態碼是 200,它表示一切順利。如果就緒狀態是 4 而且狀態碼是 200,就可以處理伺服器的資料了,而且這些資料應該就是要求的資料(而不是錯誤或者其他有問題的資訊)。因此還要在回撥方法中增加狀態檢查,如 清單 14 所示。
清單 14. 檢查 HTTP 狀態碼
清單 15. 增加一點錯誤檢查
16、讀取響應文字
現在可以確保請求已經處理完成(通過就緒狀態),伺服器給出了正常的響應(通過狀態碼),最後我們可以處理伺服器返回的資料了。返回的資料儲存在 XMLHttpRequest 物件的 responseText 屬性中。
關於 responseText 中的文字內容,比如格式和長度,有意保持含糊。這樣伺服器就可以將文字設定成任何內容。比方說,一種指令碼可能返回逗號分隔的值,另一種則使用管道符(即 | 字元)分隔的值,還有一種則返回長文字字串。何去何從由伺服器決定。
在本文使用的例子中,伺服器返回客戶的上一個訂單和客戶地址,中間用管道符分開。然後使用訂單和地址設定表單中的元素值,清單 16 給出了更新顯示內容的程式碼。
清單 16. 處理伺服器響應
多數 Web 應用程式都使用請求/響應模型從伺服器上獲得完整的 HTML 頁面。常常是點選一個按鈕,等待伺服器響應,再點選另一個按鈕,然後再等待,這樣一個反覆的過程。有了 Ajax 和 XMLHttpRequest 物件,就可以使用不必讓使用者等待伺服器響應的請求/響應模型了。
二、實現詳解
1、Web 2.0
聽到 Web 2.0 這個詞的時候,應該首先問一問 “Web 1.0 是什麼?” 雖然很少聽人提到 Web 1.0,實際上它指的就是具有完全不同的請求和響應模型的傳統 Web。比如,到 Amazon.com 網站上點選一個按鈕或者輸入搜尋項。就會對伺服器傳送一個請求,然後響應再返回到瀏覽器。該請求不僅僅是圖書和書目列表,而是另一個完整的 HTML 頁面。因此當 Web 瀏覽器用新的 HTML 頁面重繪時,可能會看到閃爍或抖動。事實上,通過看到的每個新頁面可以清晰地看到請求和響應。Web 2.0(在很大程度上)消除了這種看得見的往復互動。比如訪問 Google Maps 或 Flickr 這樣的站點。比如在 Google Maps 上,您可以拖動地圖,放大和縮小,只有很少的重繪操作。當然這裡仍然有請求和響應,只不過都藏到了幕後。作為使用者,體驗更加舒適,感覺很像桌面應用程式。這種新的感受和範型就是當有人提到 Web 2.0 時您所體會到的。
2、XMLHttpRequest 簡介
該物件幾個方法和屬性。
open():建立到伺服器的新請求。
send():向伺服器傳送請求。
abort():退出當前請求。
readyState:提供當前 HTML 的就緒狀態。
responseText:伺服器返回的請求響應文字。
3、簡單的 new
首先需要建立一個新變數並賦給它一個 XMLHttpRequest 物件例項。這在 JavaScript 中很簡單,只要對該物件名使用 new 關鍵字即可,如 清單 1 所示。
清單 1. 建立新的 XMLHttpRequest 物件
JavaScript 不要求指定變數型別。<script language="javascript" type="text/javascript"> var request = new XMLHttpRequest(); </script>
清單 2. 建立 XMLHttpRequest 的 Java 虛擬碼
XMLHttpRequest request = new XMLHttpRequest();
在 JavaScript 中用 var 建立一個變數,給它一個名字(如 “request”),然後賦給它一個新的 XMLHttpRequest 例項。此後就可以在函式中使用該物件了。
4、錯誤處理
在實際上各種事情都可能出錯,而上面的程式碼沒有提供任何錯誤處理。較好的辦法是建立該物件,並在出現問題時優雅地退出。比如,任何較早的瀏覽器(不論您是否相信,仍然有人在使用老版本的 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>
建立一個新變數 request 並賦值 false。後面將使用 false 作為判定條件,它表示還沒有建立 XMLHttpRequest 物件。
增加 try/catch 塊:
1)嘗試建立 XMLHttpRequest 物件。
2)如果失敗(catch (failed))則保證 request 的值仍然為 false。
檢查 request 是否仍為 false(如果一切正常就不會是 false)。
如果出現問題(request 是 false)則使用 JavaScript 警告通知使用者出現了問題。
程式碼非常簡單,對大多數 JavaScript 和 Web 開發人員來說,真正理解它要比讀寫程式碼花更長的時間。現在已經得到了一段帶有錯誤檢查的 XMLHttpRequest 物件建立程式碼,還可以告訴您哪兒出了問題。
5、應付 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>
建立一個新變數 request 並賦值 false。使用 false 作為判斷條件,它表示還沒有建立 XMLHttpRequest 物件。
增加 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 試驗,就應該看到已經建立的表單(沒有錯誤訊息)。
6、靜態與動態
再看一看清單 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 分鐘輸入資訊之後再顯示同樣的錯誤要好。因此,我建議編寫靜態的程式碼,讓使用者儘可能早地發現問題。
7、用 XMLHttpRequest 傳送請求
得到請求物件之後就可以進入請求/響應迴圈了。記住,XMLHttpRequest 惟一的目的是讓您傳送請求和接收響應。其他一切都是 JavaScript、CSS 或頁面中其他程式碼的工作:改變使用者介面、切換影象、解釋伺服器返回的資料。準備好 XMLHttpRequest 之後,就可以向伺服器傳送請求了。
Ajax 採用一種沙箱安全模型。因此,Ajax 程式碼(具體來說就是 XMLHttpRequest 物件)只能對所在的同一個域傳送請求。在本地機器上執行的程式碼只能對本地機器上的伺服器端指令碼傳送請求。如果讓 Ajax 程式碼在 http://www.breakneckpizza.com/ 上執行,則必須向 http://www.breakneck.com/ 中執行的指令碼傳送請求。
8、設定伺服器 URL
首先要確定連線的伺服器的 URL。這並不是 Ajax 的特殊要求,但仍然是建立連線所必需的。多數應用程式中都會結合一些靜態資料和使用者處理的表單中的資料來構造該 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 中並用 “與”(&)字元分開 [第一個引數用問號(?)和指令碼名分開]。
9、開啟請求
有了要連線的 URL 後就可以配置請求了。可以用 XMLHttpRequest 物件的 open() 方法來完成。該方法有五個引數:
request-type:傳送請求的型別。典型的值是 GET 或 POST,但也可以傳送 HEAD 請求。
url:要連線的 URL。
asynch:如果希望使用非同步連線則為 true,否則為 false。該引數是可選的,預設為 true。
username:如果需要身份驗證,則可以在此指定使用者名稱。該可選引數沒有預設值。 password:如果需要身份驗證,則可以在此指定口令。該可選引數沒有預設值。
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 就夠了,再加上 URL,這就是使用 open() 方法需要的全部內容了。10、非同步性
在一般的請求/響應模型中,比如 Web 1.0,客戶機(瀏覽器或者本地機器上執行的程式碼)向伺服器發出請求。該請求是同步的,換句話說,客戶機等待伺服器的響應。當客戶機等待的時候,至少會用某種形式通知您在等待:
沙漏(特別是 Windows 上)。
旋轉的皮球(通常在 Mac 機器上)。
應用程式基本上凍結了,然後過一段時間游標變化了。
這正是 Web 應用程式讓人感到笨拙或緩慢的原因 —— 缺乏真正的互動性。按下按鈕時,應用程式實際上變得不能使用,直到剛剛觸發的請求得到響應。如果請求需要大量伺服器處理,那麼等待的時間可能很長(至少在這個多處理器、DSL 沒有等待的世界中是如此)。
而非同步請求不 等待伺服器響應。傳送請求後應用程式繼續執行。使用者仍然可以在 Web 表單中輸入資料,甚至離開表單。沒有旋轉的皮球或者沙漏,應用程式也沒有明顯的凍結。伺服器悄悄地響應請求,完成後告訴原來的請求者工作已經結束(具體的辦法很快就會看到)。結果是,應用程式感覺不 那麼遲鈍或者緩慢,而是響應迅速、互動性強,感覺快多了。這僅僅是 Web 2.0 的一部分,但它是很重要的一部分。所有老套的 GUI 元件和 Web 設計範型都不能克服緩慢、同步的請求/響應模型。
11、傳送請求
一旦用 open() 配置好之後,就可以傳送請求了。幸運的是,傳送請求的方法的名稱要比 open() 適當,它就是 send()。
send() 只有一個引數,就是要傳送的內容。但是在考慮這個方法之前,回想一下前面已經通過 URL 本身傳送過資料了:
var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
雖然可以使用 send() 傳送資料,但也能通過 URL 本身傳送資料。事實上,GET 請求(在典型的 Ajax 應用中大約佔 80%)中,用 URL 傳送資料要容易得多。如果需要傳送安全資訊或 XML,可能要考慮使用 send() 傳送內容。如果不需要通過 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);
}
12、指定回撥方法
必須承認,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() 方法了。13、處理伺服器響應
傳送請求,使用者高興地使用 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>
它僅僅發出一些簡單的警告,告訴您伺服器什麼時候完成了任務。根據瀏覽器的不同,在表單停止彈出警告之前會看到兩次、三次甚至四次警告。這是怎麼回事呢?原來我們還沒有考慮 HTTP 就緒狀態,這是請求/響應迴圈中的一個重要部分。
14、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 程式碼,現在就會看到與預期的一樣,只顯示一次警告資訊了。15、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% 的問題。16、讀取響應文字
現在可以確保請求已經處理完成(通過就緒狀態),伺服器給出了正常的響應(通過狀態碼),最後我們可以處理伺服器返回的資料了。返回的資料儲存在 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。結果就是表單突然用客戶資訊更新了。