你正在發起了一堆異步操作的for
循環內,並且預計在for
循環完成做所有的異步操作。但是,實際上,他們中沒有一個尚未完成,因此您的doc.comments
陣列尚未填充。您正在嘗試在填充之前使用它,這就是爲什麼您會在您嘗試使用它的地方看到它。
解決此問題的最佳方法是學習如何使用Promise,然後使用Blubird的Promise.map()
或ES6 Promise.all()
等類似方法觸發多個請求,然後讓promise引擎告訴您何時完成所有請求。
轉換數據庫的短促的叫聲在使用的承諾,你可以手工編寫知道如下當一切都做:
手工編碼的回調實現
router.get('/:id', function (req, res, next) {
List.findOne({listurl: req.params.id}, function (err, doc) {
var doneCnt = 0;
if (!err && doc != null) {
for (var i = 0; i < doc.comments.length; i++) {
(function(index) {
User.findOne({Name: doc.comments[i].commenter}, function (err, data) {
++doneCnt;
if (err) {
// need some form of error handling here
doc.comments[index].gvUrl = "";
} else {
if (data) {
doc.comments[index].gvUrl = gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'});
} else {
doc.comments[index].gvUrl = 'noGravs';
}
}
// if all requests are done now
if (doneCnt === doc.documents.length) {
res.render('list', {
appTitle: doc.Title,
list: doc
});
}
});
})(i);
}
}
else {
res.status(404).render('404', {appTitle: "Book not found"});
}
});
}
此代碼識別關於異步操作的以下內容:
- 您正在觸發多個
User.findOne()
異步操作s在一個循環中。
- 這些異步操作可以按任意順序完成。
- 循環完成後,這些異步操作將不會完成。所有的循環已經完成了啓動操作。他們將在稍後完成一些不確定的時間。
- 要知道何時完成所有異步操作,它會保留一個計數器,以計數當計數達到啓動的總請求數時已完成並呈現頁面的數量。這就是「手動」方式,讓您知道何時完成所有工作。
藍鳥承諾實施
下面是它如何使用藍鳥少輝庫和轉換你的數據庫操作以支持承諾工作:
var Promise = require('bluebird');
// promisify the methods of the List and User objects
var List = Promise.promisifyAll(List);
var User = Promise.promisifyAll(User);
router.get('/:id', function (req, res, next) {
List.findOneAsync({listurl: req.params.id}).then(function(doc) {
if (!doc) {
throw "Empty Document";
}
return Promise.map(doc.comments, function(item, index, length) {
return User.findOneAsync({Name: item.commenter}).then(function(data) {
if (data) {
item.gvUrl = gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'});
} else {
item.gvUrl = 'noGravs';
}
});
}).then(function() {
return doc;
});
}).then(function(doc) {
res.render('list', {
appTitle: doc.Title,
list: doc
});
}).catch(function(err) {
res.status(404).render('404', {appTitle: "Book not found"});
});
}
這裏是如何工作的:
- 加載藍鳥承諾庫
- 向
User
和List
對象添加promisified方法,以便爲每個返回承諾的方法添加新版本。
- 致電
List.findOneAsync()
。方法名稱上的"Async"
後綴表示.promisifyAll()
添加的新方法。
- 如果沒有
doc
,那麼拋棄哪個將拒絕承諾,最後將在.catch()
中處理。 Promise是異步拋出安全的,對於異步錯誤處理非常方便。
- 致電
Promise.map()
在doc.comments
。這將自動迭代doc.comments
數組並在數組中的每個項目上調用一個迭代器(類似於Array.prototype.map()
,不同之處在於它收集了迭代器返回的所有promise,並返回一個新的promise,在所有基礎promise被解析時解析。方式,它允許所有的迭代器並行運行,並告訴你,當所有的迭代完成。
- 迭代器調用
User.findOneAsync()
並設置doc.comments[index].gvUrl
值與結果。
- 有一個在
Promise.map()
只是一個額外的.then()
處理器將該承諾的解決價值更改爲doc
對象,以便我們可以從外部承諾處理程序處獲取該承諾
- 對於來自外部承諾的成功,渲染。
- 對於來自外部承諾的錯誤,請顯示404頁面。請記住,在整個計劃的任何地方,任何被拒絕的承諾都會傳播併成爲最高層的拒絕。承諾中異步錯誤的自動傳播非常有用。
ES6承諾實施
這可能與直ES6承諾完成,無需藍鳥承諾庫,但你必須用手工做一些事情:
- 您必須提醒
List.findOne()
操作。
- 您必須提示
User.findOne()
操作。
- 您必須使用常規
doc.comments.map()
迭代,並將每個單獨的承諾收集到一個數組中,然後在該數組上使用Promise.all()
,而不是讓Promise.map()
爲您做所有這些。
下面的代碼:
// manually promisify findOne
List.findOneAsync = function(queryObj) {
return new Promise(function(resolve, reject) {
List.findOne(queryObj, function(err, data) {
if (err) return reject(err);
resolve(data);
});
}
}
User.findOneAsync = function(queryObj) {
return new Promise(function(resolve, reject) {
User.findOne(queryObj, function(err, data) {
if (err) return reject(err);
resolve(data);
});
}
}
router.get('/:id', function (req, res, next) {
List.findOneAsync({listurl: req.params.id}).then(function(doc) {
if (!doc) {
throw "Empty Document";
}
var promises = doc.comments.map(function(item, index) {
return User.findOneAsync({Name: item.commenter}).then(function(data) {
if (data) {
item.gvUrl = gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'});
} else {
item.gvUrl = 'noGravs';
}
});
});
return Promise.all(promises).then(function() {
return doc;
});
}).then(function(doc) {
res.render('list', {
appTitle: doc.Title,
list: doc
});
}).catch(function(err) {
res.status(404).render('404', {appTitle: "Book not found"});
});
}
的console.log(DOC),並確保它不是不確定的本身。但是這在var z = 0之前; – Matt
投票重新開放,因爲儘管dup解釋了爲什麼結果是「未定義」,但它沒有解釋如何在同一時間在飛行中進行許多操作以最好地解決這個特定問題。 – jfriend00