Чистый шаблон для управления многошаговыми асинхронными процессами в дереве - PullRequest
3 голосов
/ 26 января 2012

Мне нужно посетить каждый узел в дереве, выполнить некоторую асинхронную работу, а затем выяснить, когда вся асинхронная работа завершена.Вот шаги:

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

Ответы [ 3 ]

3 голосов
/ 26 января 2012

Вот обновленная версия, которая обходит дерево узлов, обрабатывает каждого дочернего элемента в начальном корневом узле, а затем рекурсивно спускается в дерево каждого дочернего элемента и обрабатывает его дочерних узлов и т. Д.

Вот демонстрационная версия 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);
}
2 голосов
/ 01 февраля 2012

Кажется, правильная модель для этой проблемы и для асинхронной работы в целом - Обещания . Идея состоит в том, что любая функция, которая будет выполнять асинхронную работу, должна возвращать объект обещания, к которому вызывающая сторона может прикреплять функции, которые должны вызываться после завершения асинхронной работы.

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

Очень аккуратно. В чем разница между отложенным объектом и объектом обещания? Хороший вопрос .

Вот как вы можете применить этот шаблон для решения этой проблемы.

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

Так много чище. Намного легче читать.

0 голосов
/ 26 января 2012

Это то, что вы, вероятно, предпочтете делать с потоками, чтобы продолжить другую работу, но, поскольку вы используете JavaScript, вам нужно обойти это с некоторой блокировкой. Один из способов - создать изначально пустой список завершенных задач, выполнить асинхронные вызовы и сделать так, чтобы каждый вызов регистрировался в списке по окончании. Пока вы ждете звонков, введите цикл с таймером и на каждой итерации проверяйте, завершен ли список завершенных задач; если так, продолжайте с другими задачами. Вы можете отказаться, если ваш цикл работает слишком долго.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...