我需要访问树中的每个节点,执行一些异步工作,然后找出所有异步工作何时完成。以下是步骤。
更新
我最终使用了一个看起来像监视器/锁定(但不是)的模式,让每个节点知道何时开始第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);
});
};
答案 0 :(得分:3)
这是一个更新版本,它遍历节点树,处理初始根节点中的每个子节点,然后递归地下降到每个子节点树中并处理其子节点等等。
// 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,因此需要通过某种阻塞来解决这个问题。一种方法是创建一个最初为空的已完成任务列表,进行异步调用,并在完成后将每个调用自身注册到列表中。在等待调用时,输入带有计时器的循环,并在每次迭代时检查完成的任务列表是否完整;如果是这样,继续其他任务。如果你的循环运行时间过长,你可能想放弃。