Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clean pattern to manage multi-step async processes on a tree

I need to visit each node in a tree, do some asynchronous work, and then find out when all of the asynchronous work has completed. Here are the steps.

  1. Visit a node and modify its children asynchronously.
  2. When async modifications to children are done, visit all children (which might require async work).
  3. When all asynchronous work for all descendants is done, do something else.

Update:

I ended up using a pattern that looks like a monitor/lock (but isn't) for each node to know when to begin step 2. I used events and attributes to keep track of all descendants of a node to know when to begin step 3.

It works, but man is this difficult to read! Is there a cleaner pattern?

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);
  });
};
like image 524
Chris Calo Avatar asked Feb 21 '26 11:02

Chris Calo


1 Answers

Here's an updated version that walks the node-tree, processing each child in the initial root node, and then descends recursively into each child's tree and processes its child nodes and so on.

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

Original Answer

Here's a basic example you might be able to use... though without the specifics of your problem, it's half psuedo-code

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);
}
like image 195
Flambino Avatar answered Feb 22 '26 23:02

Flambino



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!