用于管理树上的多步骤异步进程的干净模式

发布于 2024-12-28 20:41:22 字数 1339 浏览 0 评论 0原文

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

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

更新:

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

_失温 2025-01-04 20:41:22

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

这是一个 jsfiddle 演示

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

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);
}
我喜欢麦丽素 2025-01-04 20:41:22

对于这个问题和一般的异步工作来说,正确的模式似乎是 承诺。这个想法是,任何将执行异步工作的函数都应该返回一个 Promise 对象,调用者可以将异步工作完成时应该调用的函数附加到该对象。

jQuery 有一个很棒的 AP​​I 来实现这种模式。它称为 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 对象有什么区别? 好问题

以下是您可以如何应用此模式来解决此问题。

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

干净多了。更容易阅读。

It seems the right pattern for this problem and for async work in general is Promises. The idea is that any function that will do asynchronous work should return a promise object, to which the caller can attach functions that should be called when the asynchronous work is completed.

jQuery has a great API for implementing this pattern. It's called a jQuery.Deferred object. Here's a simple example:

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

Very tidy. What's the difference between a Deferred object and its promise object? Good question.

Here's how you might apply this pattern to solve this problem.

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

So much cleaner. So much easier to read.

游魂 2025-01-04 20:41:22

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

This is something you would probably prefer to do with threads to continue other work, but since you are using JavaScript you need to work around this with some sort of blocking. One way is make an initially empty list of finished tasks, make the asynchronous calls, and have each call register itself on the list when it is finished. While you are waiting for the calls, enter a loop with a timer, and at each iteration check if the finished tasks list is complete; if so, continue with other tasks. You may want to give up if your loop runs too long.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文