Ajax详解3-请求队列
/* 完成请求的赛跑 异步请求几乎同时发生,但却不会同步而且次序经常颠倒。 1.延迟决定胜利者 在赛跑过程中,无论是服务器还是脚本都无法使某个请求更快地得到响应。请求过程中的延迟会出现在几个阶段,而其中的多个阶段都是你无法控制的。 所有通信过程都将遵循如下相同的模式: (1)客户端计算机对服务器发起获取或修改信息的请求。 (2)将请求通过一条计算机网络发送到服务器。 (3)服务器处理请求。 (4)服务器将对请求的响应通过另一条计算机网络发送回客户端计算机 在这个请求/相应循环的过程中,每个阶段都存在外部因素的影响。 2.处理异步请求 处理请求/响应循环中的延迟问题有很多不同的方式。以下是其中几种主要的思路: (1)置之不理。 (2)关掉异步行为。 在Ajax对象中设置asynchronous=false是另外一种选择,但这个选择也可以从你的方案清单中划掉。如果在XMLHttpRequest对象上设置了同步模式,那么它会按照次序处理请求,但它是通过把请求转换为一种更加激进的阻塞模式来实现这一点的。在这种情况下,你的脚本将被迫停止运行直至请求完成,期间可能会因为响应过慢而导致脚本和浏览器被挂起。 3.在客户端队请求排队 排队是另一种可能的方案。与其一次发送多个XMLHttpRequest请求,不如等到前一个请求获得相应后再发送下一个。 */ /* 一个复制对象的辅助方法 */ function clone(myObj) { if (typeof myObj !== 'object') { return myObj; } if (myObj === null) { return myObj; } var myNewObj = {}; for (var i in myObj) { myNewObj[i] = clone(myObj[i]); } return myNewObj; } /* 用于保存队列的数组 */ var requestQueue = []; /** * 为ADS.ajaxRequest方法启用排队功能的包装对象 * @param url * @param options * @param queue * @example * ADS.ajaxRequestQueue('/your/script/', { * completeListener: function(){ * alert(this.responseText); * } * }, 'Queue1'); */ function ajaxRequestQueue(url, options, queue) { queue = queue || 'default'; // 这个对象将把可选的侦听器包装在另一个函数中 // 因此,可选的对象必须唯一。否则,如果该方法 // 被调用时使用的是共享的可选对象,那么会导致 // 陷入递归中 options = clone(options) || {}; if (!requestQueue[queue]) { requestQueue[queue] = []; } // 当前一次请求完成时,需要使用completeListener // 调用队列中的下一次请求。如果完成侦听器已经 // 有定义,那么需要首先调用它 // 取得旧侦听器 var userCompleteListener = options.completeListener; // 添加新侦听器 options.completeListener = function () { // 如果存在旧的侦听器则首先调用它 if (userCompleteListener) { // this引用的是情求对象 userCompleteListener.apply(this, arguments); } // 从队列中移除这个请求 requestQueue[queue].shift(); // 调用队列中的下一项 if (requestQueue[queue][0]) { // 请求保存在req属性中,但为防止它是 // 一个POST请求,故也需包含send选项 var q = requestQueue[queue][0].req.send( requestQueue[queue][0].send ); } }; // 如果发生了错误,应该通过调用相应的 // 错误处理方法取消队列中的其他请求 // 取得旧侦听器 var userErrorListener = options.errorListener; // 添加新侦听器 options.errorListener = function () { if (userErrorListener) { userErrorListener.apply(this, arguments); } // 由于已经调用了错误侦听器 // 股从队列中移除这个请求 requestQueue[queue].shift(); // 由于出错需要取消队列中的其余请求,但首先要调用 // 每个请求的errorListener。通过调用队列中 // 下一项的错误侦听器就会才清楚所有排队的请求,因为在 // 链中的调研那个是一次发生的 // 检测队列中是否还存在请求 if (requestQueue[queue].length) { // 取得下一项 var q = requestQueue[queue].shift(); // 中断请求 q.req.abort(); // 伪造请求对象,以便errorListener // 认为请求已经完成并相应地运行 var fakeRequest = {}; // 将status设置为0,将readyState设置为4 // 就好像请求虽然完成但却失败了一样 fakeRequest.status = 0; fakeRequest.readyState = 4; fakeRequest.responseText = null; fakeRequest.responseXML = null; // 设置错误信息,以便需要时显示 fakeRequest.statusText = 'A request in the queue received an error'; // 调用状态改变,如果readyState是4,而 // status不是200,则会调用errorListener q.error.apply(fakeRequest); } }; // 将这个请求添加到队列中 requestQueue[queue].push({ req: getRequestObject(url, options), send: options.send, error: options.errorListener }); // 如果队列的长度表明只有一个 // 项(即第一个)则调用请求 if (requestQueue[queue].length === 1) { ajaxRequest(url, options); } } window.ADS.ajaxRequestQueue = ajaxRequestQueue; //队列中的请求1 ADS.ajaxRequestQueue('/your/script/', { completeListener: function () { alert(this.responseText); } }, 'Queue1'); //队列中的请求2 ADS.ajaxRequestQueue('/your/script/', { completeListener: function () { alert(this.responseText); } }, 'Queue2'); //队列1中的请求1,要等到请求1完成 ADS.ajaxRequestQueue('/your/script/', { completeListener: function () { alert(this.responseText); } }, 'Queue1'); // 队列1与队列2会在同一时刻以异步方式运行 /* 4.令请求异步但禁用有冲突的功能 禁用功能可能是避免不协调问题的最常见方法了。当执行某些异步请求时,让用户知道后台在干什么永远是很重要的。而这通常是通过在请求等待响应期间显示“载入中”等信息或者动画来完成。在等待期间,用户可能会犹豫急不可耐而在载入完成之前有执行了相同的操作,那么就可能对程序造成潜在的破坏。 除了显示简单的“载入中”信息之外,还可以禁用程序中的某个部件,以防止用户在不耐烦的情况下重复操作。而实现这一点的唯一技巧,就是无论是响应成功,还是发生了错误都要重新启用所禁用的部件。 */ // 例如:Web应用程序中包含如下提交按钮 // <input type="submit" id="buttonID"> // 那么,就可以禁用表单提交功能 ADS.ajaxRequest('/your/script/', { loadListener: function () { // 在载入期间禁用按钮 ADS.$('buttonID').disabled = 'disabled'; }, completeListener: function () { // 当响应成功时启用按钮 ADS.$('buttonID').disabled = ''; alert(this.responseText); }, errorListener: function () { // 当发生错误时也要启用按钮 ADS.$('buttonID').disabled = ''; alert('Oops, please try again:' + this.statusText); } }); /* 这种方法的唯一问题,就是在某些情况下--比如拖放式界面功能中--如果使用它,结果差不多会与传统页面重载的工作流一样令人讨厌。所以不能再脚本等待响应期间禁用拖动功能 */ // 增加资源占用