2013-03-28 113 views
3

我有以下情况。我想从服务器加载文件A,而服务器又将尝试加载文件A1,A2,A3,... An,并且每个文件A [1-n]将依次加载其他文件,这可以继续;但结束了。 我想使用延迟对象来设置它(以避免使用async:false挂起浏览器),但加载和解析文件的递归会让我困惑于如何设置对象。此外,还有一个要求,即在继续使用级别(l-1)之前,最高递归深度级别(l)必须完成。 对于没有递归的情况,这段代码可以正常工作,但是递归的情况并没有发生。递归Ajax延迟对象

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

的基本思路是这样的:

  1. 发送ajax要求在当前水平的每个文件。
  2. 完成整个级别后,解析所有响应。
  3. 如果还有更多,递归。如果没有,请致电回拨。

由于您请求的某些文件不存在(并且这是预期的),因此您需要创建自己的延迟。然后,您可以从ajax donefail回调中解决它们,从而有效地忽略故障。

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

注:因为我是从我们的看法添加功能getFile,范围/关闭的问题都不再是问题(因为每个dfd变量现在是在一个函数范围内),这样的代码是少了几分困惑,我想。问题是非常普遍的回路scopeissue

代码:

// 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(); 
}; 
+0

你好,我明白了什么代码做什么,但第一级之后,它只是停止。我已将代码放在http://www.virtuality.gr/AGG/EaZD-WebGL/test_lego.html并在控制台上输出。我尝试添加一个小缓存,因为几个文件重复,但我现在已经评论。我想,为了组成整个下载文件(递归完成后),我需要另一个数组。 – gaitat

+0

我不确定编辑做了什么。我得到了同样的结果。只是第一级被解析。 pathList变量包含可能存在文件的目录。不是所有的路径,只有一个。所以在通过pathList的for循环中,只有一个会成功。其余的将失败。 – gaitat

+0

好的,我认为我在编辑中做了一些愚蠢的事情:)但它很接近。将在一分钟后再次编辑。 –

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元素已经完成发生。