任何人都可以幫助我,並向我解釋爲什麼它不起作用?
TL;博士 - 該問題是通過在一個異步函數(dataset.findOne
)運行的環路引起的,可以在循環完成之前不完整。您需要像async
(正如其他答案所建議的)那樣使用庫,或者像第一個代碼示例中那樣使用回調來處理此問題。
循環執行的同步功能
這可以聽起來迂腐,但要了解在同步和異步世界循環之間的區別是很重要的。考慮這個同步帶:
var numbers = [];
for(i = 0 ; i < 5 ; i++){
numbers[i] = i*2;
}
console.log("array:",numbers);
在我的系統,該電源輸出:
array: [ 0, 2, 4, 6, 8 ]
這是因爲分配給numbers[i]
發生之前的循環能夠迭代。對於任何同步(「阻塞」)分配/功能,您將以這種方式得到結果。
爲了說明,讓我們試試這個代碼:
function sleep(time){
var stop = new Date().getTime();
while(new Date().getTime() < stop + time) {}
}
for(i = 0 ; i < 5 ; i++){
sleep(1000);
}
如果您的手錶,或在一些console.log
消息拋出,你會看到「休眠」,持續5秒。
這是因爲while
循環在sleep
塊......它迭代直到time
毫秒已經超過,然後再返回到for循環控制。
循環通過異步函數
你的問題的根源在於dataset.findOne
是異步的...這意味着它就把控制權回到循環之前的數據庫返回的結果。方法findOne
採取回調(匿名function(err, doc)
)創建一個閉包。
描述閉包在這裏超出了這個答案的範圍,但如果你搜索本網站或使用你最喜歡的搜索引擎「JavaScript閉包」,你會得到噸信息。
但是,底線是異步調用將查詢發送到數據庫。因爲事務需要一些時間並且它有一個可以接受查詢結果的回調函數,所以它將控制權交給for循環。 (重要的是:這是節點的「事件循環」和它與「異步編程」的交集,節點通過允許異步行爲提供非阻塞環境)。
讓我們來看一個例子異步問題可以絆倒我們:
for(i = 0 ; i < 5 ; i++){
setTimeout(
function(){console.log("I think I is: ", i);} // anonymous callback
,1 // wait 1ms before using the callback function
)
}
console.log("I am done executing.")
你會得到輸出,看起來像這樣:
I am done executing.
I think I is: 5
I think I is: 5
I think I is: 5
I think I is: 5
I think I is: 5
這是因爲setTimeout
得到一個函數調用...所以,即使我們只說「等待一毫秒「,那仍然是lo比循環重複5次並移動到最後的console.log
行要花費更多的時間。
然後會發生什麼,最後一行在之前觸發,第一個匿名回調觸發。當它確實發生火災時,循環已經結束,並且i
等於5
。因此,您在此看到的是循環已完成,並且繼續前進,即使交給setTimeout
的匿名函數仍可訪問i
的值。 (這是行動中的「關閉」...)
如果我們採用這個概念並使用它來考慮你的第二個「破」代碼示例,我們可以看到你爲什麼沒有得到你期望的結果。
app.get('/api/two', function(req, res){
dataset.count(function(err, count){
var docs = []
for(i = 0; i < 2 ; i++){
var rand = Math.floor(Math.random() * count);
// THIS IS ASYNCHRONOUS.
// findOne gets a callback...
// hands control back to the for loop...
// and later pushes info into the "doc" array...
// too late for res.json, at least...
dataset.findOne({'index':rand}, function(err, doc){
docs.push(doc);
});
}
// THE LOOP HAS ENDED BEFORE any of the findOne callbacks fire...
// There's nothing in 'docs' to be sent back to the client. :(
res.json(docs);
});
});
原因async
,承諾和其他類似的庫是一個很好的工具是他們幫助解決你所面臨的問題。 async
和承諾可以將在這種情況下創建的「回調地獄」變成一個相對乾淨的解決方案...它更容易閱讀,更容易看到異步情況發生的地方,以及當你需要進行編輯時,你沒有擔心你在/編輯/等等的回調級別。
非常感謝你的詳細解釋! – Idealist