3

我有以下情况。我想从服务器加载文件 A,该服务器又会尝试加载文件 A1、A2、A3、... An 并且每个文件 A[1-n] 将依次加载其他文件,这可以继续;但它已经结束了。我想使用延迟对象来设置它(以免使用 async: false 挂起浏览器),但是加载和解析文件的递归让我对如何设置对象感到困惑。此外,还要求必须完成最高递归深度级别 (l),然后我才能继续级别 (l-1)。对于没有递归的情况,此代码有效,但递归情况使我无法理解。

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);
4

2 回答 2

2

基本思想是这样的:

  1. 为当前级别的每个文件发送ajax请求。
  2. 完成整个级别后,解析所有响应。
  3. 如果有更多,递归。如果没有,请调用回调。

由于您请求的某些文件不存在(这是意料之中的),您需要创建自己的 Deferreds。然后你可以从 ajaxdonefail回调中解决它们,有效地忽略失败。

另外,我根据您的要求添加了一个缓存对象。该对象将 url 映射到 Promise。当您将done回调附加到已解决的承诺时,会立即使用相同的response参数调用回调。这是缓存内容的好方法,因为第一个请求不必完成就可以在缓存中,因为您正在缓存请求而不是响应。因此,如果您在第一个请求完成之前请求同一个文件 4 次,它仍然只会导致一次ajax调用。

注意:由于我添加了 function getFile,我们评论中的作用域/闭包问题不再是问题(因为每个dfd变量现在都在函数作用域中),所以我认为代码不会那么混乱。问题是非常常见的循环范围 问题

代码:

// 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();
};
于 2013-04-01T14:37:32.197 回答
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都已完成时发生的回调。

于 2013-03-28T13:07:19.773 回答