文 - 篇  访客 -

使用JS和Ajax发出异步请求


  分类:04 技术开发  / 
更新:2023-08-14 09:49:09  /  创建:2023-08-04 16:06:47
不要删除

本文介绍了如何创建能够适应不同浏览器的XMLHttpRequest实例,建立和发送请求,并响应服务器。您将开始接触最基本和基础性的有关Ajax的全部对象和编程方法:XMLHttpRequest对象。该对象实际上仅仅是一个跨越所有Ajax应用程序的公共线程,您可能已经预料到,只有彻底理解该对象才能充分发挥编程的潜力。 

XMLHttpRequest简介

XMLHttpRequest 是JS的一个对象。它是介绍 Web 2.0、Ajax 和大部分其他内容的核心。下面给出该对象的几个方法和属性:

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

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

3、abort():退出当前请求。 

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

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

用XMLHttpRequest能够做什么呢,值得注意的是这些方法和属性都与发送请求及处理响应有关。事实上,如果看到XMLHttpRequest的所有方法和属性,就会发现它们都与非常简单的请求/响应模型有关。用好该对象可以彻底改变您的应用程序。

 创建XMLHttpRequest对象实例

首先需要创建一个新变量并赋给它一个XMLHttpRequest对象实例。这在JS中很简单,只要对该对象名使用new关键字即可.创建新的XMLHttpRequest对象:var request = new XMLHttpRequest(); 

创建 XMLHttpRequest的 Java伪代码:XMLHttpRequest request = new XMLHttpRequest(); 

错误与跨浏览器处理

在实际上各种事情都可能出错,而上面的代码没有提供任何错误处理。较好的办法是创建该对象,并在出现问题时优雅地退出。比如,任何较早的浏览器都不支持 XMLHttpRequest,您需要让这些用户知道有些地方出了问题。下面说明如何创建该对象,以便在出现问题的时候发出 JavaScript 警告。

创建具有错误处理能力的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 块: 

3、尝试创建 XMLHttpRequest 对象。 

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

5、检查 request 是否仍为 false(如果一切正常就不会是 false)。 

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

现在已经得到了一段带有错误检查的XMLHttpRequest对象创建代码,还可以告诉您哪儿出了问题。 

增加对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 块: 

3、尝试创建XMLHttpRequest对象。

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

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

这样修改代码之后再使用InternetExplorer试验,就应该看到已经创建的表单(没有错误消息)。

静态代码与动态代码

代码都直接嵌套在script标记中,不放到方法或函数体中的JS代码称为静态JS。这种情况代码是在页面显示给用户之前的某个时候运行。虽然根据规范不能完全精确地知道这些代码何时运行对浏览器有什么影响,但是可以保证这些代码在用户能够与页面交互之前运行,这也是多数Ajax程序员创建XMLHttpRequest对象的一般方式。

将 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 之前需要调用该方法。因此还需要使用XMLHttpRequest的创建方法。

function getCustomerInfo() {

 createRequest();

}

此方式惟一的问题是推迟了错误通知,这也是多数 Ajax 程序员不采用这一方法的原因。如果使用静态JS,用户在点击页面的时候很快就会看到错误信息。这样也很烦人,是不是?可能令用户错误地认为您的Web应用程序不能在他的浏览器上运行。不过,当然要比他们花费了10 分钟输入信息之后再显示同样的错误要好。因此,我建议编写静态的代码,让用户尽可能早地发现问题。 

用XMLHttpRequest发送请求

得到请求对象之后就可以进入请求/响应循环了。记住XMLHttpRequest 惟一的目的是让您发送请求和接收响应。其他一切都是JS、CSS 或页面中其他代码的工作。如改变用户界面、切换图像、解释服务器返回的数据。准备好XMLHttpRequest 之后,就可以向服务器发送请求了。Ajax 采用一种沙箱安全模型。因此Ajax 代码(具体来说就是XMLHttpRequest对象)只能对所在的同一个域发送请求。记住:在本地机器上运行的代码只能对本地机器上的服务器端脚本发送请求。如让Ajax代码在www.google.com上运行,则必须在www.google.com中运行的脚本发送请求。

设置服务器URL

首先要确定连接的服务器的 URL。这并不是Ajax的特殊要求,但仍然是建立连接所必需的,显然现在您应该知道如何构造URL了。多数应用程序中都会结合一些静态数据和用户处理的表单中的数据来构造该URL。 

建立请求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” 的表单字段的值赋给它。下面展示了这个表单的HTML,其中可以看到phone字段及其id属性。 

 <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();" />

span>

   </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> 

还要注意,当用户输入电话号码或者改变电话号码时,将触发getCustomerInfo() 方法。该方法取得电话号码并构造存储在url变量中的 URL字符串。记住,由于Ajax代码是沙箱型的,因而只能连接到同一个域,实际上URL中不需要域名。

escape() 方法是一个顶级JS方法,并不与任何对象关联。使用escape方法可以将属性值手工添加到URL中。escape方法编码指定字符串中的特定字符,并返回新字符串。它用于转义不能用明文正确发送的任何字符。比如电话号码中的空格将被转换成字符%20,从而能够在URL中传递这些字符。 

可以根据需要添加任意多个参数。比如需要增加另一个参数,只需要将其附加到URL中并用“与”(&)字符分开,第一个参数用问号(?)和脚本名分开。 

打开请求

open() 是打开吗?Internet 开发人员对open() 方法到底做什么没有达成一致。但它实际上并不是打开一个请求。如果监控 HTML/Ajax 页面及其连接脚本之间的网络和数据传递,当调用 open() 方法时将看不到任何通信。不清楚为何选用了这个名字,但显然不是一个好的选择。有了要连接的URL后就可以配置请求了。可以用XMLHttpRequest对象的open()方法来完成。该方法有五个参数:

1、request-type:发送请求的类型。典型的值是 GET 或 POST,但也可以发送 HEAD 请求。 

2、url:要连接的 URL。 

3、asynch:如果希望使用异步连接则为 true,否则为 false。该参数是可选的,默认为 true。 

4、username:如果需要身份验证,则可以在此指定用户名。该可选参数没有默认值。 

5、password:如果需要身份验证,则可以在此指定口令。该可选参数没有默认值。 

通常使用其中的前三个参数。事实上即使需要异步连接,也应该指定第三个参数为true。这是默认值,但坚持明确指定请求是异步的还是同步的更容易理解。 

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() 方法需要的全部内容了。

挑战异步性

编写和使用异步代码,您应该明白为什么open() 的最后一个参数这么重要。在一般的请求/响应模型中,比如 Web 1.0,客户机(浏览器或者本地机器上运行的代码)向服务器发出请求。该请求是同步的,换句话说客户机等待服务器的响应。当客户机等待的时候,至少会用某种形式通知您在等待:

1、沙漏(特别是 Windows 上)。 

2、旋转的皮球(通常在 Mac 机器上)。 

3、应用程序基本上冻结了,然后过一段时间光标变化了。

这正是Web应用程序让人感到笨拙或缓慢的原因——缺乏真正的交互性。按下按钮时,应用程序实际上变得不能使用,直到刚刚触发的请求得到响应。如果请求需要大量服务器处理,那么等待的时间可能很长。而异步请求不等待服务器响应。发送请求后应用程序继续运行。用户仍然可以在Web表单中输入数据,甚至离开表单。没有旋转的皮球或者沙漏,应用程序也没有明显的冻结。服务器悄悄地响应请求,完成后告诉原来的请求者工作已经结束。结果是应用程序感觉不那么迟钝或者缓慢,而是响应迅速、交互性强,感觉快多了。这仅仅是Web 2.0的一部分,但它是很重要的一部分。所有Web2.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()发送内容。如果不需要通过send()传递数据,则只要传递null 作为该方法的参数即可。因此您会发现在本文中的例子中只需要这样发送请求。

 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。

首先一定要理解这些代码中的流程。建立其请求然后发出请求。此外,因为是异步请求,所以JavaScript 方法(例子中的 getCustomerInfo())不会等待服务器。因此代码将继续执行,就是说,将退出该方法而把控制返回给表单。用户可以继续输入信息,应用程序不会等待服务器。这就提出了一个有趣的问题:服务器完成了请求之后会发生什么?答案是什么也不发生,至少对现在的代码而言如此!显然这样不行,因此服务器在完成通过 XMLHttpRequest 发送给它的请求处理之后需要某种指示说明怎么做。 

在JS中引用函数

JavaScript 是一种弱类型的语言,可以用变量引用任何东西。因此如果声明了一个函数 updatePage(),JavaScript 也将该函数名看作是一个变量。换句话说,可用变量名updatePage在代码中引用函数。 现在onreadystatechange属性该登场了。该属性允许指定一个回调函数。回调允许服务器反向调用 Web 页面中的代码。它也给了服务器一定程度的控制权,当服务器完成请求之后,会查看XMLHttpRequest 对象,特别是onreadystatechange属性。然后调用该属性指定的任何方法。之所以称为回调是因为服务器向网页发起调用,无论网页本身在做什么。比方说,可能在用户坐在椅子上手没有碰键盘的时候调用该方法,但是也可能在用户输入、移动鼠标、滚动屏幕或者点击按钮时调用该方法。它并不关心用户在做什么。这就是称之为异步的原因:用户在一层上操作表单,而在另一层上服务器响应请求并触发 onreadystatechange 属性指定的回调方法。因此需要像如下一样在代码中指定该方法。

 设置回调方法

 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 属性设置为要运行的函数名。这样,当服务器处理完请求后就会自动调用该函数。也不需要担心该函数的任何参数。我们从一个简单的方法开始,回调方法的代码

 <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)。输入电话号码然后离开该字段,将看到一个弹出的警告窗口,但是点击 OK 又出现了……

根据浏览器的不同,在表单停止弹出警告之前会看到两次、三次甚至四次警告。这是怎么回事呢?原来我们还没有考虑 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,它表示服务器响应已经完成,可以安全地使用响应数据了。基于此,回调方法中的第一行应该如下检查就绪状态 

function updatePage() {

     if (request.readyState == 4)

       alert("Server is done!");

}

修改后就可以保证服务器的处理已经完成。尝试运行新版本的Ajax代码,现在就会看到与预期的一样,只显示一次警告信息了。

HTTP 状态码 

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

function updatePage() {

     if (request.readyState == 4)

       if (request.status == 200)

         alert("Server is done!");

}

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

增加一点错误检查

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 中的文本内容,比如格式和长度,有意保持含糊。这样服务器就可以将文本设置成任何内容。比方说,一种脚本可能返回逗号分隔的值,另一种则使用管道符(即 | 字符)分隔的值,还有一种则返回长文本字符串。何去何从由服务器决定。在本文使用的例子中,服务器返回客户的上一个订单和客户地址,中间用管道符分开。然后使用订单和地址设置表单中的元素值,下面给出了更新显示内容的代码,处理服务器响应。 

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并使用JS的split() 方法从管道符分开。得到的数组放到response中。数组中的第一个值——上一个订单——用 response[0] 访问,被设置为ID 为“order” 的字段的值。第二个值 response[1],即客户地址,则需要更多一点处理。因为地址中的行用一般的行分隔符(“n”字符)分隔,代码中需要用 XHTML 风格的行分隔符
来代替。替换过程使用 replace() 函数和正则表达式完成。最后,修改后的文本作为 HTML 表单 div 中的内部 HTML。

responseXML属性

现在介绍XMLHttpRequest 的另一个重要属性responseXML。如果服务器选择使用 XML 响应则该属性包含(也许您已经猜到)XML 响应。处理 XML 响应和处理普通文本有很大不同,涉及到解析、文档对象模型(DOM)和其他一些问题。对于很多简单的 Ajax 应用程序 responseText 就够了,但是其实通过 Ajax 应用程序也能很好地处理 XML。

结束语

您可能对XMLHttpRequest感到有点厌倦了,我很少看到一整篇文章讨论一个对象,特别是这种简单的对象。但是您将在使用Ajax编写的每个页面和应用程序中反复使用该对象。坦白地说,关于 XMLHttpRequest 还真有一些可说的内容。其实有很多Ajax工具箱。这些工具箱实际上隐藏了本文所述的很多细节,使得 Ajax 编程更容易。您也许会想,既然有这么多工具箱为何还要对底层的细节编码。答案是你如果不知道应用程序在做什么,就很难发现应用程序中的问题。因此不要忽略这些细节或者简单地浏览一下,如果便捷华丽的工具箱出现了错误,您就不必挠头或者发送邮件请求支持了。如果了解如何直接使用 XMLHttpRequest,就会发现很容易调试和解决最奇怪的问题。只有让其解决您的问题,工具箱才是好东西。因此请熟悉 XMLHttpRequest 吧。事实上,如果您有使用工具箱的 Ajax 代码,可以尝试使用 XMLHttpRequest 对象及其属性和方法重新改写。这是一种不错的练习,可以帮助您更好地理解其中的原理。


不要删除

是日已过,命亦随减,如少水魚,斯有何乐?