您的位置:首页 > Web前端 > JavaScript

Ajax 学习第二课-使用 JavaScript 和 Ajax 发出异步请求

2009-04-03 22:05 627 查看
多数Web应用程序都使用请求/响应模型从服务器上获得完整的HTML页面。常常是点击一个按钮,等待服务器响应,再点击另一个按钮,然后再等待,这样一个反复的过程。有了Ajax和XMLHttpRequest对象,就可以使用不必让用户等待服务器响应的请求/响应模型了。本文介绍了如何创建能够适应不同浏览器的XMLHttpRequest实例,建立和发送请求,并响应服务器。
本系列的第一课中,我们介绍了Ajax应用程序的基本概念。其中的核心是很多您可能已经了解的技术:JavaScript、HTML和XHTML、一点动态HTML以及DOM(文档对象模型)。本文将放大其中的一点,把目光放到具体的Ajax细节上。
本文中,您将开始接触最基本和基础性的有关Ajax的全部对象和编程方法:XMLHttpRequest对象。该对象实际上仅仅是一个跨越所有Ajax应用程序的公共线程,您可能已经预料到,只有彻底理解该对象才能充分发挥编程的潜力。事实上,有时您会发现,要正确地使用XMLHttpRequest,显然不能使用XMLHttpRequest。这到底是怎么回事呢?
当我们访问GoogleMaps或Flickr这样的站点(到这些支持Web2.0和Ajax站点的链接请参阅参考资料)。比如在GoogleMaps上,您可以拖动地图,放大和缩小,只有很少的重绘操作。当然这里仍然有请求和响应,只不过都藏到了幕后。作为用户,体验更加舒适,感觉很像桌面应用程序。这种新的感受和范型就是当有人提到Web2.0时您所体会到的。
需要关心的是如何使这些新的交互成为可能。显然,仍然需要发出请求和接收响应,但正是针对每次请求/响应交互的HTML重绘造成了缓慢、笨拙的Web交互的感受。因此很清楚,我们需要一种方法使发送的请求和接收的响应只包含需要的数据而不是整个HTML页面。惟一需要获得整个新HTML页面的时候就是希望用户看到新页面的时候。
但多数交互都是在已有页面上增加细节、修改主体文本或者覆盖原有数据。这些情况下,Ajax和Web2.0方法允许在不更新整个HTML页面的情况下发送和接收数据。对于那些经常上网的人,这种能力可以让您的应用程序感觉更快、响应更及时,让他们不时地光顾您的网站。
XMLHttpRequest简介
要真正实现这种绚丽的奇迹,必须非常熟悉一个JavaScript对象,即XMLHttpRequest。这个小小的对象实际上已经在几种浏览器中存在一段时间了,它是本专栏今后几个月中要介绍的Web2.0、Ajax和大部分其他内容的核心。为了让您快速地大体了解它,下面给出将要用于该对象的很少的几个方法和属性。

open():建立到服务器的新请求。

send():向服务器发送请求。

abort():退出当前请求。

readyState:提供当前HTML的就绪状态。

responseText:服务器返回的请求响应文本。

如果不了解这些(或者其中的任何一个),您也不用担心,后面几篇文章中我们将介绍每个方法和属性。
下面开始具体介绍了:
首先需要创建一个新变量并赋给它一个XMLHttpRequest对象实例。这在JavaScript中很简单,这在第一课中已有介绍,但是为了学习方便这里再介绍一下:
当然此步骤主要解决的问题就是浏览器兼容,经验证发现Microsoft支持Ajax,但是其XMLHttpRequest版本有不同的称呼。事实上,它将其称为几种不同的东西。如果使用较新版本的InternetExplorer,则需要使用对象Msxml2.XMLHTTP,而较老版本的InternetExplorer则使用Microsoft.XMLHTTP。我们需要支持这两种对象类型(同时还要支持非Microsoft浏览器)。请看看清单4,它在前述代码的基础上增加了对Microsoft的支持。

清单4.增加对Microsoft浏览器的支持
<scriptlanguage="javascript"type="text/javascript">
varrequest=false;
try{
request=newXMLHttpRequest();
}catch(trymicrosoft){
try{
request=newActiveXObject("Msxml2.XMLHTTP");
}catch(othermicrosoft){
try{
request=newActiveXObject("Microsoft.XMLHTTP");
}catch(failed){
request=false;
}
}
}
if(!request)
alert("ErrorinitializingXMLHttpRequest!");
</script>
很容易被这些花括号迷住了眼睛,因此下面分别介绍每一步:

创建一个新变量request并赋值false。使用false作为判断条件,它表示还没有创建XMLHttpRequest对象。

增加try/catch块:
尝试创建XMLHttpRequest对象。

如果失败(catch(trymicrosoft)):
尝试使用较新版本的Microsoft浏览器创建Microsoft兼容的对象(Msxml2.XMLHTTP)。

如果失败(catch(othermicrosoft))尝试使用较老版本的Microsoft浏览器创建Microsoft兼容的对象(Microsoft.XMLHTTP)。

如果失败(catch(failed))则保证request的值仍然为false。

检查request是否仍然为false(如果一切顺利就不会是false)。

如果出现问题(request是false)则使用JavaScript警告通知用户出现了问题。

所有这些代码都直接嵌套在script标记中。像这种不放到方法或函数体中的JavaScript代码称为静态JavaScript。就是说代码是在页面显示给用户之前的某个时候运行。(虽然根据规范不能完全精确地知道这些代码何时运行对浏览器有什么影响,但是可以保证这些代码在用户能够与页面交互之前运行。)这也是多数Ajax程序员创建XMLHttpRequest对象的一般方式。
当然,顺便提一下,你也可以把js内容单独放在一个js文件中,这时只需在页面<head>标签中添按如下方式添加一条语句就OK了:
<scripttype="text/javascript"src="<%=request.getContextPath()%>/javascript/registerValidate.js"></script>
XMLHttpRequest发送请求
得到请求对象之后就可以进入请求/响应循环了。记住,XMLHttpRequest惟一的目的是让您发送请求和接收响应。其他一切都是JavaScript、CSS或页面中其他代码的工作:改变用户界面、切换图像、解释服务器返回的数据。准备好XMLHttpRequest之后,就可以向服务器发送请求了。Hiatus记得步骤吗??不记得没关系,因为前边的介绍主要是让你有个印象,但是从这里开始你就要明白每个步骤的操作和它的含义了。
第一步,设置服务器URL
首先要确定连接的服务器的URL。这并不是Ajax的特殊要求,但仍然是建立连接所必需的,也就是建立一个字符串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.打开请求

functiongetCustomerInfo(){

varphone=document.getElementById("phone").value;

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

request.open("GET",url,true);

}

一旦设置好了URL,其他就简单了。多数请求使用
GET
就够了(后面的文章中将看到需要使用
POST
的情况),再加上URL,这就是使用
open()
方法需要的全部内容了。清单9中的request就是一开始的时候产生的XMLHttpRequest对象,不要看不明白哈。代码中也使用了escape()方法,escape(),encodeURI()和encodeURIComponent()是在Javascript中用于编码字符串的三个常用的方法,而他们之间的异同却困扰了很多的Javascript初学者,但这不是本文重点,有兴趣的朋友可以自己去研究一下。本文不做介绍。
第三步,发送请求
一旦用
open()
配置好之后,就可以发送请求了。幸运的是,发送请求的方法的名称要比
open()
适当,它就是
send()

send()
只有一个参数,就是要发送的内容。但是在考虑这个方法之前,回想一下前面已经通过URL本身发送过数据了:
varurl="/cgi-local/lookupCustomer.php?phone="+escape(phone);

虽然可以使用
send()
发送数据,但也能通过URL本身发送数据。事实上,
GET
请求(在典型的Ajax应用中大约占80%)中,用URL发送数据要容易得多。如果需要发送安全信息或XML,可能要考虑使用
send()
发送内容(本系列的后续文章中将讨论安全数据和XML消息)。如果不需要通过
send()
传递数据,则只要传递
null
作为该方法的参数即可。因此您会发现在本文中的例子中只需要这样发送请求(参见清单10)。
清单10.发送请求

functiongetCustomerInfo(){

varphone=document.getElementById("phone").value;

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

request.open("GET",url,true);

request.send(null);

}

第四步,指定回调方法和处理服务器响应(请注意,此步骤事实上是在第三步之前完成的,安排在第四步主要是为了读者理解方便,不影响你的阅读)
现在我们所做的只有很少一点是新的、革命性的或异步的。必须承认,open()方法中“true”这个小小的关键字建立了异步请求。但是除此之外,这些代码与用Javaservlet及JSP、PHP或Perl编程没有什么两样。那么Ajax和Web2.0最大的秘密是什么呢?秘密就在于XMLHttpRequest的一个简单属性onreadystatechange。
这就提出了一个有趣的问题:服务器完成了请求之后会发生什么?答案是什么也不发生,至少对现在的代码而言如此!显然这样不行,因此服务器在完成通过
XMLHttpRequest
发送给它的请求处理之后需要某种指示说明怎么做。
JavaScript中引用函数
JavaScript是一种弱类型的语言,可以用变量引用任何东西。因此如果声明了一个函数
updatePage()
,JavaScript也将该函数名看作是一个变量。换句话说,可用变量名
updatePage
在代码中引用函数。
现在
onreadystatechange
属性该登场了。该属性允许指定一个回调函数。回调允许服务器(猜得到吗?)反向调用Web页面中的代码。它也给了服务器一定程度的控制权,当服务器完成请求之后,会查看
XMLHttpRequest
对象,特别是
onreadystatechange
属性。然后调用该属性指定的任何方法。之所以称为回调是因为服务器向网页发起调用,无论网页本身在做什么。比方说,可能在用户坐在椅子上手没有碰键盘的时候调用该方法,但是也可能在用户输入、移动鼠标、滚动屏幕或者点击按钮时调用该方法。它并不关心用户在做什么。
这就是称之为异步的原因:用户在一层上操作表单,而在另一层上服务器响应请求并触发
onreadystatechange
属性指定的回调方法。因此需要像清单11一样在代码中指定该方法。
清单11.设置回调方法

functiongetCustomerInfo(){

varphone=document.getElementById("phone").value;

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

request.open("GET",url,true);

request.onreadystatechange=updatePage;

request.send(null);

}

需要特别注意的是该属性在代码中设置的位置——它是在调用
send()
之前设置的。发送请求之前必须设置该属性,这样服务器在回答完成请求之后才能查看该属性。现在剩下的就只有编写
updatePage()
方法了,这是本文最后一节要讨论的重点。
具体设置回调方法:
现在我们已经看到如何告诉服务器完成后应该做什么:将
XMLHttpRequest
对象的
onreadystatechange
属性设置为要运行的函数名。这样,当服务器处理完请求后就会自动调用该函数。也不需要担心该函数的任何参数。我们从一个简单的方法开始,如清单12所示。
清单12.回调方法的代码

<scriptlanguage="javascript"type="text/javascript">

varrequest=false;

try{

request=newXMLHttpRequest();

}catch(trymicrosoft){

try{

request=newActiveXObject("Msxml2.XMLHTTP");

}catch(othermicrosoft){

try{

request=newActiveXObject("Microsoft.XMLHTTP");

}catch(failed){

request=false;

}

}

}

if(!request)

alert("ErrorinitializingXMLHttpRequest!");

functiongetCustomerInfo(){

varphone=document.getElementById("phone").value;

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

request.open("GET",url,true);

request.onreadystatechange=updatePage;

request.send(null);

}

functionupdatePage(){

alert("Serverisdone!");

}

</script>

上面代码仅仅发出一些简单的警告,告诉您服务器什么时候完成了任务。在自己的网页中试验这些代码,然后在浏览器中打开(如果希望查看该例中的XHTML,请参阅清单8)。输入电话号码然后离开该字段,将看到一个弹出的警告窗口(如图3所示),但是点击OK又出现了……
根据浏览器的不同,在表单停止弹出警告之前会看到两次、三次甚至四次警告。这是怎么回事呢?原来我们还没有考虑HTTP就绪状态,这是请求/响应循环中的一个重要部分。
在Firefox1.5中,您会看到以下就绪状态:

1

2

3

4

在Safari2.0.1中看到的状态:

2

3

4

哎,这就是令人讨厌的浏览器不兼容啊不管她咯。下面还是让我们认识一下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编程的常识,而是希望了解更多内容,就需要熟悉就绪状态、状态代码和请求本身的内容。当应用程序出现问题时——这种问题总是存在——那么如果能够正确理解就绪状态、如何生成一个HEAD请求或者400的状态代码的确切含义,就可以在5分钟内调试出问题,而不是在各种挫折和困惑中度过5个小时。
对于Ajax编程,需要直接处理的惟一状态就是就绪状态4,它表示服务器响应已经完成,可以安全地使用响应数据了。基于此,回调方法中的第一行应该如清单13所示。
清单13.检查就绪状态

functionupdatePage(){

if(request.readyState==4)

alert("Serverisdone!");

}

修改后就可以保证服务器的处理已经完成。尝试运行新版本的Ajax代码,现在就会看到与预期的一样,只显示一次警告信息了。
一般都要求就绪状态为4的时候才进行相应处理,因为所有的文档和规范都强调,只有在就绪状态为4时数据才可以安全使用。相信我,当就绪状态为3时,您很少能找到无法从responseText属性获取数据的情况。然而,在应用程序中将自己的逻辑依赖于就绪状态3可不是什么好主意——一旦您编写了依赖于就绪状态3的完整数据的的代码,几乎就要自己来负责当时的数据不完整问题了。
比较好的做法是向用户提供一些反馈,说明在处于就绪状态3时,很快就会有响应了。尽管使用alert()之类的函数显然不是什么好主意——使用Ajax然后使用一个警告对话框来阻塞用户显然是错误的——不过您可以在就绪状态发生变化时更新表单或页面中的域。例如,对于就绪状态1来说要将进度指示器的宽度设置为25%,对于就绪状态2来说要将进度指示器的宽度设置为50%,对于就绪状态3来说要将进度指示器的宽度设置为75%,当就绪状态为4时将进度指示器的宽度设置为100%(完成)。
当然,正如您已经看到的一样,这种方法非常聪明,但它是依赖于浏览器的。在Opera上,您永远都不会看到前两个就绪状态,而在Safari上则没有第一个(1)。

HTTP状态码(更多状态码参见:http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)
虽然清单13中的代码看起来似乎不错,但是还有一个问题——如果服务器响应请求并完成了处理但是报告了一个错误怎么办?要知道,服务器端代码应该明白它是由Ajax、JSP、普通HTML表单或其他类型的代码调用的,但只能使用传统的Web专用方法报告信息。而在Web世界中,HTTP代码可以处理请求中可能发生的各种问题。
比方说,您肯定遇到过输入了错误的URL请求而得到404错误码的情形,它表示该页面不存在。这仅仅是HTTP请求能够收到的众多错误码中的一种(完整的状态码列表请参阅参考资料中的链接)。表示所访问数据受到保护或者禁止访问的403和401也很常见。无论哪种情况,这些错误码都是从完成的响应得到的。换句话说,服务器履行了请求(即HTTP就绪状态是4)但是没有返回客户机预期的数据。
因此除了就绪状态外,还需要检查HTTP状态。我们期望的状态码是200,它表示一切顺利。如果就绪状态是4而且状态码是200,就可以处理服务器的数据了,而且这些数据应该就是要求的数据(而不是错误或者其他有问题的信息)。因此还要在回调方法中增加状态检查,如清单14所示。
清单14.检查HTTP状态码

functionupdatePage(){

if(request.readyState==4)

if(request.status==200)

alert("Serverisdone!");

}

为了增加更健壮的错误处理并尽量避免过于复杂,可以增加一两个状态码检查,请看一看清单15中修改后的
updatePage()
版本。
清单15.增加一点错误检查

functionupdatePage(){

if(request.readyState==4)

if(request.status==200)

alert("Serverisdone!");

elseif(request.status==404)

alert("RequestURLdoesnotexist");

else

alert("Error:statuscodeis"+request.status);

}

现在将
getCustomerInfo()
中的URL改为不存在的URL看看会发生什么。应该会看到警告信息说明要求的URL不存在——好极了!很难处理所有的错误条件,但是这一小小的改变能够涵盖典型Web应用程序中80%的问题。
读取响应文本
现在可以确保请求已经处理完成(通过就绪状态),服务器给出了正常的响应(通过状态码),最后我们可以处理服务器返回的数据了。返回的数据保存在
XMLHttpRequest
对象的
responseText
属性中。
关于
responseText
中的文本内容,比如格式和长度,有意保持含糊。这样服务器就可以将文本设置成任何内容。比方说,一种脚本可能返回逗号分隔的值,另一种则使用管道符(即
|
字符)分隔的值,还有一种则返回长文本字符串。何去何从由服务器决定。
在本文使用的例子中,服务器返回客户的上一个订单和客户地址,中间用管道符分开。然后使用订单和地址设置表单中的元素值,清单16给出了更新显示内容的代码。
清单16.处理服务器响应

functionupdatePage(){

if(request.readyState==4){

if(request.status==200){

varresponse=request.responseText.split("|");

document.getElementById("order").value=response[0];

document.getElementById("address").innerHTML=

response[1].replace(//n/g,"

");

}else

alert("statusis"+request.status);

}

}

首先,得到
responseText
并使用JavaScript
split()
方法从管道符分开。得到的数组放到
response
中。数组中的第一个值——上一个订单——用
response[0]
访问,被设置为ID为“order”的字段的值。第二个值
response[1]
,即客户地址,则需要更多一点处理。因为地址中的行用一般的行分隔符(“/n”字符)分隔,代码中需要用XHTML风格的行分隔符
<br/>
来代替。替换过程使用
replace()
函数和正则表达式完成。最后,修改后的文本作为HTML表单
div
中的内部HTML。结果就是表单突然用客户信息更新了,如图4所示。
4.收到客户数据后的BreakNeck表单

结束本文之前,我还要介绍
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对象及其属性和方法重新改写。这是一种不错的练习,可以帮助您更好地理解其中的原理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: