Javascript Clean模式管理树上的多步骤异步进程

Javascript Clean模式管理树上的多步骤异步进程,javascript,algorithm,dom,asynchronous,tree,Javascript,Algorithm,Dom,Asynchronous,Tree,我需要访问树中的每个节点,执行一些异步工作,然后找出所有异步工作何时完成。以下是步骤 访问节点并异步修改其子节点 对子项进行异步修改后,访问所有子项(可能需要异步工作) 完成所有子体的所有异步工作后,请执行其他操作 更新: 我最终使用了一种模式,它看起来像监视器/锁(但不是),让每个节点知道何时开始第2步。我使用事件和属性跟踪节点的所有后代,以知道何时开始第3步 它是有效的,但人就是这么难读!有更干净的模式吗 function step1(el) { // recursive var all

我需要访问树中的每个节点,执行一些异步工作,然后找出所有异步工作何时完成。以下是步骤

  • 访问节点并异步修改其子节点
  • 对子项进行异步修改后,访问所有子项(可能需要异步工作)
  • 完成所有子体的所有异步工作后,请执行其他操作
  • 更新:

    我最终使用了一种模式,它看起来像监视器/锁(但不是),让每个节点知道何时开始第2步。我使用事件和属性跟踪节点的所有后代,以知道何时开始第3步

    它是有效的,但人就是这么难读!有更干净的模式吗

    function step1(el) { // recursive
      var allDone = false;
      var monitor = new Monitor();
      var lock = monitor.lock(); // obtain a lock
      $(el).attr("step1", ""); // step1 in progress for this node
    
      // fires each time a descendant node finishes step 1
      $(el).on("step1done", function (event) {
        if (allDone) return;
        var step1Descendants = $(el).find("[step1]");
        if (step1Descendants.length === 0) {
          // step 1 done for all descendants (so step 2 is complete)
          step3(el); // not async
          allDone = true;
        }
      });
    
      // fires first time all locks are unlocked
      monitor.addEventListener("done", function () {
        $(el).removeAttr("step1"); // done with step 1
        step2(el); // might have async work
        $(el).trigger("step1done");
      });
    
      doAsyncWork(el, monitor); // pass monitor to lock/unlock
      lock.unlock(); // immediately checks if no other locks outstanding
    };
    
    function step2(el) { // visit children
      $(el).children().each(function (i, child) {
        step1(child);
      });
    };
    

    这是您可能更愿意使用线程来继续其他工作的事情,但是由于您使用的是JavaScript,因此需要通过某种阻塞来解决这一问题。一种方法是创建一个已完成任务的初始空列表,进行异步调用,并在每个调用完成时将其自身注册到列表中。在等待调用时,输入一个带有计时器的循环,并在每次迭代时检查完成的任务列表是否完整;如果是,请继续执行其他任务。如果循环运行太长,您可能希望放弃。

    这里有一个更新版本,它遍历节点树,处理初始根节点中的每个子节点,然后递归地下降到每个子节点的树并处理其子节点,依此类推

    //传递根节点和要调用的回调
    //处理完整个树后
    函数processTree(根节点,回调){
    var i,l,待定;
    //如果没有子节点,只需调用回调
    //马上回来
    if((挂起=rootNode.childNodes.length)==0){
    回调();
    返回;
    }
    //创建一个函数以在某项操作完成时调用
    函数完成(){
    --挂起的| |回调();
    }
    //对于每个子节点
    对于(i=0,l=rootNode.childNodes.length;i

    原始答案 下面是一个基本示例,您可以使用。。。虽然没有问题的细节,但它只是半个伪代码

    function doAsyncTreeStuff(rootNode, callback) {
        var pending = 0;
    
        // Callback to handle completed DOM node processes
        // When pending is zero, the callback will be invoked
        function done() {
            --pending || callback();
        }
    
        // Recurse down through the tree, processing each node
        function doAsyncThingsToNode(node) {
            pending++;
    
            // I'm assuming the async function takes some sort of
            // callback it'll invoke when it's finished.
            // Here, we pass it the `done` function
            asyncFunction(node, done);
    
            // Recursively process child nodes
            for( var i = 0 ; i < node.children.length ; i++ ) {
                doAsyncThingsToNode(node.children[i]);
            }
        }
    
        // Start the process
        doAsyncThingsToNode(rootNode);
    }
    
    函数doAsyncTreeStuff(根节点,回调){
    var待定=0;
    //回调以处理已完成的DOM节点进程
    //当pending为零时,将调用回调
    函数完成(){
    --挂起的| |回调();
    }
    //通过树向下递归,处理每个节点
    函数doAsyncThingsToNode(节点){
    挂起++;
    //我假设异步函数需要某种
    //完成后将调用的回调。
    //在这里,我们传递'done'函数
    异步函数(节点,完成);
    //递归处理子节点
    对于(var i=0;i
    对于这个问题和异步工作,通常的正确模式是。其思想是,任何将执行异步工作的函数都应该返回一个promise对象,调用方可以将异步工作完成时应该调用的函数附加到该对象

    jQuery有一个很好的API来实现这个模式。这叫做物体。下面是一个简单的例子:

    function asyncWork() {
      var deferred = $.Deferred();
      setTimeout(function () {
        // pass arguments via the resolve method
        deferred.resolve("Done.");
      }, 1000);
      return deferred.promise();
    }
    
    asyncWork().then(function (result) {
      console.log(result);
    });
    
    非常整洁。延迟对象和承诺对象之间有什么区别

    下面是如何应用此模式来解决此问题

    function step1(el) { // recursive
      var deferred = $.Deferred();
    
      // doAsyncWork needs to return a promise
      doAsyncWork(el).then(function () {
        step2(el).then(function () {
          step3(el); // not async
          deferred.resolve();
        });
      });
      return deferred.promise();
    };
    
    function step2(el) { // visit children
      var deferred = $.Deferred();
      var childPromises = [];
      $(el).children().each(function (i, child) {
        childPromises.push(step1(child));
      });
    
      // When all child promises are resolved…
      $.when.apply(this, childPromises).then(function () {
        deferred.resolve();
      });
      return deferred.promise();
    };
    

    这么干净。阅读起来容易多了。

    是否保留一个正在进行的工作计数器?添加作业、增量计数器;完成一项工作,减少计数器。当计数器为零时,您就完成了。看起来承诺就是我要寻找的模式。现在正在研究一个解决方案……这肯定会有所帮助,但它遗漏了一个我没有解释清楚的要点。我将更新这个问题。@ChristopherJamesCalo假设我的示例中的
    asyncFunction
    是第一个修改子节点的节点,只需将for循环移动到
    asyncFunction
    的回调中即可<代码>异步函数
    本身的工作原理与示例基本相同(递增/递减计数器,完成后调用回调)。这样,
    asyncFunction
    修改子节点(步骤1),一旦完成,循环将递归到子节点(步骤2)。@ChristopherJamesCalo实际上,忽略上面的内容-这并不准确。重点是,我的示例中的基本设置(计数器和函数)将适用于任何此类工作。您正在执行一个两步异步过程,因此您需要在这两个位置使用类似的代码(或者将其分解为一个类)。一个用于跟踪子节点修改,另一个用于跟踪后续子节点处理。或者,可以修改每个子节点,然后在回电之前访问(步骤1+2)。Flambino,您是否可以修改您的答案以反映您的最新评论?我很难从你答案中的代码跳到多步骤版本。特别是,我想知道是否有一种更清晰的方法可以知道在子体上的所有异步工作何时完成,而不必使用属性和事件。@ChristopherJamesCalo我明白了
    function step1(el) { // recursive
      var deferred = $.Deferred();
    
      // doAsyncWork needs to return a promise
      doAsyncWork(el).then(function () {
        step2(el).then(function () {
          step3(el); // not async
          deferred.resolve();
        });
      });
      return deferred.promise();
    };
    
    function step2(el) { // visit children
      var deferred = $.Deferred();
      var childPromises = [];
      $(el).children().each(function (i, child) {
        childPromises.push(step1(child));
      });
    
      // When all child promises are resolved…
      $.when.apply(this, childPromises).then(function () {
        deferred.resolve();
      });
      return deferred.promise();
    };