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

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

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

在 Web 請求中使用 XMLHttpRequestjavascript

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

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

2006 年 2 月 16 日java

  • expand內容

本系列的上一期文章(請參閱 參考資料 中的連接),咱們介紹了 Ajax 應用程序,考察了推進 Ajax 應用程序的基本概念。其中的核心是不少您可能已經瞭解的技術:JavaScript、HTML 和 XHTML、一點動態 HTML 以及 DOM(文檔對象模型)。本文將放大其中的一點,把目光放到具體的 Ajax 細節上。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 正常工做

靜態與動態

再看一看清單 13 和 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 到 一、二、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 表單
收到客戶數據後的 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 請求和以不一樣的格式發送數據。請開始編寫代碼吧,一個月後咱們再繼續討論。

參考資料

學習

得到產品和技術

  • 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 以及如何將二者結合起來的完整參考。

討論

相關文章
相關標籤/搜索