[英]Javascript: How to control flow with async recursive tree traversal?
我需要在樹上進行遞歸,以使用異步操作在特定節點上執行操作。 如何控制流量,以便在完成后可以訪問節點?
這是一個示例情況:
data = {
name: "deven",
children: [
{ name: "andrew" },
{ name: "donovan" },
{ name: "james",
children: [
{ name: "donatello" },
{ name: "dan" }
]
},
{ name: "jimmy",
children: [
{ name: "mike" },
{ name: "dank" }
]
}
]
};
我有一個函數,它的目標是遍歷樹並將所有以'd'開頭的名稱大寫。 之后,我想將樹傳遞給另一個函數來做更多工作(可能刪除所有名稱以'a'開頭的節點),但只有在完成初始處理之后:
function capitalize_d(node) {
if(node.name === "d") {
node.name = node.name.toUpperCase();
}
if(node.children != null) {
for(var i = 0; i < node.children.length; i++) {
capitalize_d(node.children[i]);
}
}
}
function remove_a(node) {
}
capitalize_d(data);
// Should only get called after all the d's have been capitalized.
remove_a(data);
上面的代碼工作正常,因為capitalize_d是阻塞的。 如果capitalize_d
異步遞歸,我們如何保證remove_a
在完成后被調用? 注意在capitalize_d
調用setTimeout
。
function capitalize_d(node) {
setTimeout(function() {
if(node.name === "d") {
node.name = node.name.toUpperCase();
}
if(node.children != null) {
for(var i = 0; i < node.children.length; i++) {
capitalize_d(node.children[i]);
}
}
}, 1);
}
function remove_a(node) {
}
capitalize_d(data);
// Should only get called after all the d's have been capitalized.
remove_a(data);
問題是我們對樹的不同分支的處理同時被解雇了,並且無法確定它何時最終完成處理樹。
我怎么解決這個問題?
讓我總結一下我對你的要求的理解:
capitalize_d
和remove_a
), 我花了10年或更長時間設計實時嵌入式軟件,並且相信我,這個領域的要求比大多數網絡程序員在他們的整個生活中所經歷的任何事情都更加苛刻和苛刻。 這讓我警告你,你似乎在這里走錯了方向。
我可以想象,你的問題是在某種有意義的結構中組織一組個人數據。 某些進程收集隨機信息位(在您的示例中稱為“節點”),並且在某些時候您希望將所有這些節點放入一致的單一數據結構(示例中的分層樹)。
換句話說,您手頭有三個任務:
我的建議: 不要試圖同時進行收購和生產 。
只是為了讓您了解您前往的噩夢:
根據操作的觸發方式,樹可能永遠不會被給定的操作完全處理。 假設控制軟件忘記在幾個節點上調用capitalize_d
, remove_a
將永遠不會得到綠燈
相反,如果您隨機觸發樹,很可能會多次處理某些節點,除非您跟蹤操作覆蓋率以防止對給定節點應用相同的轉換兩次
如果你想讓remove_a
處理能夠啟動,你可能必須阻止控制軟件發送更多的capitalize_d
請求,否則燈光可能會永遠停留在紅色狀態。 你最終會以這種或那種方式對你的請求進行流量控制(或者更糟糕的是:你不會做任何事情,如果操作流程遠離你偶然遇到的最佳位置,你的系統很可能會凍死)。
如果一個操作改變了樹的結構(就像remove_a
顯然那樣),你必須防止並發訪問。 至少,您應該從remove_a
正在處理的節點開始鎖定子樹,否則您將允許處理可能異步更改和/或銷毀的子樹。
嗯,這是可行的。 我見過很好的男人賺大錢做這個主題的變化。 他們通常每周花幾個晚上在他們的電腦前吃比薩餅,但是,嘿,這就是你如何告訴來自食客的人群中的硬漢黑客,對吧?...
我假設你在這里發布這個問題意味着你真的不想這樣做。 現在,如果你的老板,嗯,引用一個着名的機器人,我不能騙你的機會,但是...你有我的同情心。
現在認真的人..這是我解決問題的方法。
1)在給定的時間點拍攝數據的快照
您可以使用盡可能多的標准清除原始數據(最后一次數據采集太舊,輸入不正確,任何允許您構建最小樹的數據)。
2)使用快照構建樹,然后在給定的快照上依次應用任何capitalize_d,remove_a和camelize_z操作。
同時,數據采集過程將繼續收集新節點或更新現有節點,准備拍攝下一個快照。
此外,您可以向前移動一些處理。 顯然, capitalize_d
沒有利用樹結構,因此您可以在構建樹之前將capitalize_d
應用於快照中的每個節點。 您甚至可以更早地應用某些轉換,即每個收集的樣本。 這可以為您節省大量處理時間和代碼復雜性。
結束一點理論上的嘮叨,
數據生成過程可以按需觸發(比如當最終用戶點擊“給我看東西”按鈕時),在這種情況下,反應性會相當差:用戶會被困在看沙漏或任何Web2.0性感旋轉輪子建造和處理樹所需的時間(讓我們說7-8秒)。
或者您可以定期激活數據生成過程(每10秒為其提供一個新快照,安全地高於數據集的平均處理時間)。 然后,“show me something”按鈕將顯示最后一組完成的數據。 立即回答,但數據可能比最后收到的樣本早10秒。
我很少看到這種情況不被認為是可接受的,特別是當您生成一堆復雜數據時,操作員需要幾十秒才能消化。
從理論上講,我的方法會失去一些反應性,因為處理的數據會稍微過時,但並發訪問方法可能會導致軟件速度更慢(大多數肯定是5-10倍更大和更笨)。
我知道這篇文章很老了,但它出現在搜索結果中,單獨的響應沒有提供一個工作示例,所以這里是我最近做過的一些修改版本......
function processTree(rootNode, onComplete) {
// Count of outstanding requests.
// Upon a return of any request,
// if this count is zero, we know we're done.
var outstandingRequests = 0;
// A list of processed nodes,
// which is used to handle artifacts
// of non-tree graphs (cycles, etc).
// Technically, since we're processing a "tree",
// this logic isn't needed, and could be
// completely removed.
//
// ... but this also gives us something to inspect
// in the sample test code. :)
var processedNodes = [];
function markRequestStart() {
outstandingRequests++;
}
function markRequestComplete() {
outstandingRequests--;
// We're done, let's execute the overall callback
if (outstandingRequests < 1) { onComplete(processedNodes); }
}
function processNode(node) {
// Kickoff request for this node
markRequestStart();
// (We use a regular HTTP GET request as a
// stand-in for any asynchronous action)
jQuery.get("/?uid="+node.uid, function(data) {
processedNodes[node.uid] = data;
}).fail(function() {
console.log("Request failed!");
}).always(function() {
// When the request returns:
// 1) Mark it as complete in the ref count
// 2) Execute the overall callback if the ref count hits zero
markRequestComplete();
});
// Recursively process all child nodes (kicking off requests for each)
node.children.forEach(function (childNode) {
// Only process nodes not already processed
// (only happens for non-tree graphs,
// which could include cycles or multi-parent nodes)
if (processedNodes.indexOf(childNode.uid) < 0) {
processNode(childNode);
}
});
}
processNode(rootNode);
}
這是使用QUnit的示例用法:
QUnit.test( "async-example", function( assert ) {
var done = assert.async();
var root = {
uid: "Root",
children: [{
uid: "Node A",
children: [{
uid: "Node A.A",
children: []
}]
},{
uid: "Node B",
children: []
}]
};
processTree(root, function(processedNodes) {
assert.equal(Object.keys(processedNodes).length, 4);
assert.ok(processedNodes['Root']);
assert.ok(processedNodes['Node A']);
assert.ok(processedNodes['Node A.A']);
assert.ok(processedNodes['Node B']);
done();
});
});
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.