/*
完成请求的赛跑
异步请求几乎同时发生,但却不会同步而且次序经常颠倒。
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 = [];
var ADS = [];
/**
* 为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);
}
}
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);
}
});
/*
这种方法的唯一问题,就是在某些情况下--比如拖放式界面功能中--如果使用它,结果差不多会与传统页面重载的工作流一样令人讨厌。所以不能再脚本等待响应期间禁用拖动功能
*/
console