递归Ajax延迟对象

时间:2013-03-28 12:49:35

标签: jquery jquery-deferred promise

我有以下情况。我想从服务器加载文件A,然后服务器将尝试加载文件A1,A2,A3,... An和每个文件A [1-n]将依次加载其他文件,这可以继续;但它已经结束了。 我想使用延迟对象进行设置(以便不使用async:false挂起浏览器)但是加载和解析文件的递归使我对如何设置对象感到困惑。此外,要求在我继续使用级别(l-1)之前必须完成最高递归深度级别(l)。 对于没有递归的情况,这段代码可以工作,但递归的情况不包括在内。

var loadFile = function (index, url, scope, callback) {
    $.ajax ({url: url, type: 'GET', dataType: 'text'})
    .done (function (responseText) {
        // store response in array
        scope.requests[index] = responseText;
    })
    .fail (function (xhr, status, error) {
        scope.requests[index] = 'error';
    })
    .always (function (responseText) {
        // loop through entire response array from the beginning
        for (var j = 0; j < scope.requests.length; j++)
        {
            if (scope.requests[j] === 'unprocessed') return;
            else if (scope.requests[j] === 'error')
                scope.requests[j] = 'done';
            else if (scope.requests[j] !== 'done')
            {
                parseFile (scope.requests[j], scope, callback);
                scope.requests[j] = 'done';
            }
        }

        // if all are done then reset the array and run the callback
        delete scope.requests;

        if (callback) callback();
    });
}

var parseFile = function (responseText, scope, callback) {
    var lines = responseText.split("\n");

    for (var l = 0; l < lines.length; l++) {
        var line = lines[l];
        line = line.replace (/^\s+/, ''); // remove leading white space
        if (line.charAt(0) === '1') // file reference
        {
            var attrs = line.split (/\s+/);

            // the file will exist in any of the paths in pathList
            for (var i = 0; i < scope.pathList.length; i++) {
                scope.requests.push ('unprocessed');
                loadFile (++index, scope.pathList[i] + attrs[14], scope, callback);
            }
        }
    }
}

var index = 0;
var this.requests = [];
this.requests.push ('unprocessed');
loadFile (index, fileAi, this, callback);

2 个答案:

答案 0 :(得分:2)

基本理念是:

  1. 针对当前级别的每个文件发送ajax请求。
  2. 完成整个级别后,解析所有响应。
  3. 如果有更多,请递归。如果没有,请拨回回电。
  4. 由于您请求的某些文件不存在(这是预期的),您需要创建自己的Deferreds。然后,您可以从ajax donefail回调中解决它们,从而有效地忽略失败。

    另外,我根据您的要求添加了一个缓存对象。该对象将URL映射到promises。当您将done回调附加到已经解析的承诺时,将使用相同的response参数立即调用回调。这是一种很好的缓存方式,因为第一个请求不必完成,因为它是缓存请求而不是响应。因此,如果您在第一个请求完成之前请求相同的文件4次,它仍然只会导致一次ajax次呼叫。

    注意:由于我添加了函数getFile,因此我们的注释中的范围/闭包问题不再是问题(因为每个dfd变量现在都在函数范围内),所以代码是我想,有点不那么混乱了。问题是非常常见的循环scope issue

    代码:

    // load all files, starting with startUrl.  
    // call callback when done.
    var loadAll = function(startUrl, callback) {
        var pathList = []; // assuming this has some base urls in it.
        var dfds = []; // dfds for the current level.
        var urls = [startUrl]; // urls for current level.
        var responses = []; // responses for current level.
        var cache = {}; // object to map urls to promises.
    
        // given the responseText, add any referenced urls to the urls array
        var parseFile = function (responseText) {
            var lines = responseText.split("\n");
    
            for (var l = 0; l < lines.length; l++) {
                var line = lines[l];
                line = line.replace (/^\s+/, ''); // remove leading white space
                if (line.charAt(0) === '1') // file reference
                {
                    var attrs = line.split (/\s+/);
    
                    // the file will exist in any of the paths in pathList
                    for (var i = 0; i < pathList.length; i++) {
                        // add new path to urls array
                        urls.push (pathList[i] + attrs[14]);
                    }
                }
            }
        };
    
        // load one file.
        // check cache for existing promise for the url.
        var getFile = function(url) {
            var dfd;
    
            if(cache.hasOwnProperty(url)){
                // use cached promise.
                // if it is already resolved, any callback attached will be called immediately.
                dfd = cache[url];
                dfds.push(cache[url]);
            } else {
                dfd = $.Deferred();
                $.ajax ({url: url, type: 'GET', dataType: 'text'}).done(function(response){
                    // resolve and pass response.
                    dfd.resolve(response);
                }).fail(function(){
                    // resolve and pass null, so this error is ignored.
                    dfd.resolve(null);
                });
                dfds.push(dfd.promise());
                cache[url] = dfd.promise();
            }
    
            // when the request is done, add response to array.
            dfd.done(function(response) {
                if(response){
                    // add to responses array.
                    // might want to check if the same response is already in the array.
                    responses.push(response);
                }
            });
        };
    
        // request each file in the urls array.
        // recurse when all requests done, or call callback.
        var loadLevel = function () {
            dfds = [];
            responses = [];
    
            for (var l = 0; l < urls.length; l++) {
                getFile(urls[l]);
            }
    
            $.when.apply($, dfds).done(function(){
                // entire level is done loading now.
                // each done function above has been called already, 
                // so responses array is full.
                urls = [];
    
                // parse all the responses for this level.
                // this will refill urls array.
                for (var i = 0; i < responses.length; i++) {
                    parseFile(responses[i]);
                }
    
                if(urls.length === 0) {
                    // done
                    callback();
                } else {
                    // load next level
                    loadLevel();
                }
            });
        };
    
        // load first level
        loadLevel();
    };
    

答案 1 :(得分:0)

我认为你不能用代码结构化的“逐级”块来实现,因为编写代码总是会在递归展开之前尝试完成整个分支,即给定这个结构:

     1
    / \
   2   6
  /|\  |
 3 4 5 7

它会跟随显示的数字顺序中的节点,而不是[1] [2 6] [3 4 5 7](或者你的意思是[3 4 5 7] [2 6] [1]?)

我无法提供完整的解决方案,只是提示我认为会有所帮助。

  1. 您需要为每个级别创建一个数组,包含该级别每个请求文件的延迟对象。

  2. 您无法使用jqXHR对象,因为您还会在.fail案例中进行递归,因此您必须自己创建一个单独的$.Deferred()然后.resolve处理程序中的.always

  3. 使用$.when.apply($, myArray).done(...)触发只有在myArray所有元素完成后才会发生的回调。