2015-10-23 52 views
2

可以說我正在爲一組值運行某種長操作。將使用setTimeout防止堆棧增長?

啓動這個操作是startNext()

,並在其中執行最後一行的功能是把自己所以它的遞歸調用,像這樣:

function startNext(){ 
    var val = getNextValue() 
    workOnValue(val) 
     .then(doSomeMoreWork) 
     .then(doMoreStuff) 
     .then(moree) 
     .then(startNext); 
} 

這將使棧成長爲尾部遞歸在JS(尚未)中不起作用。 會改變最後一行:

.then(function(){setTimeout(startNext, 0)}); 

更好地工作? 它會不會填充堆棧,因爲它會爲事件循環添加一個新的操作?

+0

你檢查過它嗎?你有沒有嘗試分析你的代碼? – Joseph

回答

1

是,setTimeout將防止堆棧增長,因爲目前的功能完成和「遞歸」調用本身已被放置在事件隊列。它也會運行得更慢,因爲它沒有被直接調用,而是通過隊列進行處理。

爲了演示和證明這個嘗試使用Node的實驗。

將下面的代碼示例放到文件中,並將simple標誌切換到底部。你會發現recurseSimple功能運行速度非常快,並且非常快速地吹出堆棧。 recurseTimeout運行速度較慢,但​​會永久運行。

function recurseSimple(count) { 
// Count: 15269, error: bootstrap_node.js:392 
// RangeError: Maximum call stack size exceeded 
    try { 
    if (count % 10000 === 0) { 
     console.log('Running count:', count); 
    } 
    recurseSimple(count + 1); 
    } catch (e) { 
    console.log(`Simple count: ${count}, error:`, e); 
    } 
} 

function recurseTimeout(count) { 
    // No stack exceeded 
    try { 
    if (count % 10000 === 0) { 
     console.log('Running count:', count); 
    } 
    setTimeout(recurseTimeout.bind(null, count + 1), 0); 
    } catch (e) { 
    console.log(`Timeout count: ${count}, error:`, e); 
    } 
} 

const simple = false; 

if (simple) { 
    recurseSimple(0); 
} else { 
    recurseTimeout(0); 
} 

完全相同的主體適用於承諾。爲了保持這一點儘可能簡單,我沒有在這裏使用承諾。

0

將使用setTimeout防止堆棧增長?

不是。它阻止了一段時間,但仍然存在遞歸。它看起來像

startNext() - >的setTimeout(10,startNext) - > 10ms的暫停(事件隊列) - > startNext()

這使得來電的號碼堆棧,以打破它,但仍然遞歸功能保持良好。

1

then處理程序被推出執行上下文棧,所以它已經做你的建議是什麼:

onFulfilled或onRejected不能調用,直到執行上下文棧只包含平臺的代碼。 [3.1]。

這適用於A +承諾。

這裏是3.1注爲清楚:

這裏「平臺代碼」是指發動機,環境和承諾的實現代碼。在實踐中,這個要求確保onFulfilled和onRejected異步執行,在事件循環開始之後執行,然後在新堆棧之後執行。這可以用被實現一個「宏觀任務」機構如的setTimeoutsetImmediate,或具有「微任務」機構如MutationObserverprocess.nextTick。由於承諾實現被認爲是平臺代碼,因此它本身可能包含調用處理程序的任務調度隊列或「蹦牀」。

Promises A+