清理模式以管理树上的多步异步过程

时间:2012-01-25 20:14:25

标签: javascript algorithm dom asynchronous tree

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

  1. 访问节点并异步修改其子节点。
  2. 完成对子项的异步修改后,请访问所有子项(可能需要异步工作)。
  3. 当完成所有后代的所有异步工作后,请执行其他操作。
  4. 更新

    我最终使用了一个看起来像监视器/锁定(但不是)的模式,让每个节点知道何时开始第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);
      });
    };
    

3 个答案:

答案 0 :(得分:3)

这是一个更新版本,它遍历节点树,处理初始根节点中的每个子节点,然后递归地下降到每个子节点树中并处理子节点等等。

Here's a jsfiddle demo

// Pass the root node, and the callback to invoke
// when the entire tree has been processed
function processTree(rootNode, callback) {
    var i, l, pending;

    // If there are no child nodes, just invoke the callback
    // and return immediately
    if( (pending = rootNode.childNodes.length) === 0 ) {
        callback();
        return;
    }

    // Create a function to call, when something completes
    function done() {
        --pending || callback();
    }

    // For each child node
    for( i = 0, l = rootNode.childNodes.length ; i < l ; i++ ) {
        // Wrap the following to avoid the good ol'
        // index-closure-loop issue. Pass the function
        // a child node
        (function (node) {

            // Process the child node asynchronously.
            // I'm assuming the function takes a callback argument
            // it'll invoke when it's done.
            processChildNodeAsync(node, function () {

                // When the processing is done, descend into
                // the child's tree (recurse)
                processTree(node, done);

            });

        }(rootNode.childNodes[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);
}

答案 1 :(得分:2)

这个问题以及异步工作的正确模式似乎是Promises。我们的想法是,任何进行异步工作的函数都应该返回一个promise对象,调用者可以将函数附加到异步工作完成时应该调用的函数。

jQuery有一个很好的API来实现这个模式。它被称为jQuery.Deferred对象。这是一个简单的例子:

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

非常整洁。 Deferred对象与其promise对象之间有什么区别? Good question

以下是您可以应用此模式来解决此问题的方法。

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

太干净了。这么容易阅读。

答案 2 :(得分:0)

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