SOURCE

/*
 完成请求的赛跑

 异步请求几乎同时发生,但却不会同步而且次序经常颠倒。

 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 命令行工具 X clear

                    
>
console