我正在尝试编写一个程序,该程序使用mongoose从mongo数据库获取文档,并使用API处理它们,然后使用处理结果编辑数据库中的每个文档。我的问题是我有问题,因为我完全不了解nodejs和异步。这是我的代码:
Model.find(function (err, tweets) {
if (err) return err;
for (var i = 0; i < tweets.length; i++) {
console.log(tweets[i].tweet);
api.petition(tweets[i].tweet)
.then(function(res) {
TweetModel.findOneAndUpdate({_id: tweets[i]._id}, {result: res}, function (err, tweetFound) {
if (err) throw err;
console.log(tweetFound);
});
})
.catch(function(err) {
console.log(err);
})
}
})
问题是在findOneAndUpdate中,推文是未定义的,因此无法找到该ID。有解决方案吗感谢
答案 0 :(得分:4)
您真正缺少的核心是Mongoose API方法也使用"Promises",但您似乎只是使用回调从文档或旧示例中复制。解决方案是转换为仅使用Promises。
Model.find({},{ _id: 1, tweet: 1}).then(tweets =>
Promise.all(
tweets.map(({ _id, tweet }) =>
api.petition(tweet).then(result =>
TweetModel.findOneAndUpdate({ _id }, { result }, { new: true })
.then( updated => { console.log(updated); return updated })
)
)
)
)
.then( updatedDocs => {
// do something with array of updated documents
})
.catch(e => console.error(e))
除了回调的一般转换之外,主要更改是使用Promise.all()
来解析Array.map()
而不是for
的结果所处理的.find()
的输出}循环。这实际上是您尝试中遇到的最大问题之一,因为for
实际上无法控制异步函数何时解析。另一个问题是“混合回调”,但这就是我们通常只使用Promises来解决的问题。
在Array.map()
内,我们从API调用中返回Promise
,并链接到实际更新文档的findOneAndUpdate()
。我们还使用new: true
来实际返回修改后的文档。
Promise.all()
允许“数组Promise”解析并返回一组结果。您将其视为updatedDocs
。这里的另一个优点是内部方法将以“并行”而不是串联方式触发。这通常意味着更快的分辨率,但需要更多的资源。
另请注意,我们使用{ _id: 1, tweet: 1 }
的“投影”仅返回Model.find()
结果中的这两个字段,因为这些是其余调用中使用的唯一字段。当您不使用其他值时,这可以节省返回每个结果的整个文档。
您只需从Promise
返回findOneAndUpdate()
即可,但我只是添加console.log()
,这样您就可以看到输出正在点击。
正常的生产使用应该没有它:
Model.find({},{ _id: 1, tweet: 1}).then(tweets =>
Promise.all(
tweets.map(({ _id, tweet }) =>
api.petition(tweet).then(result =>
TweetModel.findOneAndUpdate({ _id }, { result }, { new: true })
)
)
)
)
.then( updatedDocs => {
// do something with array of updated documents
})
.catch(e => console.error(e))
另一个“调整”可能是使用Promise.map()
的“bluebird”实现,它将公共Array.map()
到Promise
(s)实现与控制“并发”的能力结合起来“运行并行呼叫:
const Promise = require("bluebird");
Model.find({},{ _id: 1, tweet: 1}).then(tweets =>
Promise.map(tweets, ({ _id, tweet }) =>
api.petition(tweet).then(result =>
TweetModel.findOneAndUpdate({ _id }, { result }, { new: true })
),
{ concurrency: 5 }
)
)
.then( updatedDocs => {
// do something with array of updated documents
})
.catch(e => console.error(e))
“并行”的替代将按顺序执行。如果太多结果导致过多的API调用和写回数据库的调用,则可以考虑这一点:
Model.find({},{ _id: 1, tweet: 1}).then(tweets => {
let updatedDocs = [];
return tweets.reduce((o,{ _id, tweet }) =>
o.then(() => api.petition(tweet))
.then(result => TweetModel.findByIdAndUpdate(_id, { result }, { new: true })
.then(updated => updatedDocs.push(updated))
,Promise.resolve()
).then(() => updatedDocs);
})
.then( updatedDocs => {
// do something with array of updated documents
})
.catch(e => console.error(e))
我们可以使用Array.reduce()
将承诺“链接”在一起,允许它们按顺序解析。请注意,结果数组保留在范围内并换出,最后.then()
附加到连接链的末尾,因为您需要这样一种技术来“收集”来自Promises的结果解析在“链”中的不同点
在现代环境中,从NodeJS V8.x实际上是当前的LTS版本并且已经有一段时间了,你实际上支持async/await
。这使您可以更自然地编写流程
try {
let tweets = await Model.find({},{ _id: 1, tweet: 1});
let updatedDocs = await Promise.all(
tweets.map(({ _id, tweet }) =>
api.petition(tweet).then(result =>
TweetModel.findByIdAndUpdate(_id, { result }, { new: true })
)
)
);
// Do something with results
} catch(e) {
console.error(e);
}
如果资源有问题,甚至可能按顺序处理:
try {
let cursor = Model.collection.find().project({ _id: 1, tweet: 1 });
while ( await cursor.hasNext() ) {
let { _id, tweet } = await cursor.next();
let result = await api.petition(tweet);
let updated = await TweetModel.findByIdAndUpdate(_id, { result },{ new: true });
// do something with updated document
}
} catch(e) {
console.error(e)
}
还注意到findByIdAndUpdate()
也可以用作匹配_id
已经隐含,因此您不需要将整个查询文档作为第一个参数。
作为最后一点,如果您根本不需要更新的文档,那么bulkWrite()
是更好的选择,并允许写入通常在一个请求中在服务器上处理:
Model.find({},{ _id: 1, tweet: 1}).then(tweets =>
Promise.all(
tweets.map(({ _id, tweet }) => api.petition(tweet).then(result => ({ _id, result }))
)
).then( results =>
Tweetmodel.bulkWrite(
results.map(({ _id, result }) =>
({ updateOne: { filter: { _id }, update: { $set: { result } } } })
)
)
)
.catch(e => console.error(e))
或通过async/await
语法:
try {
let tweets = await Model.find({},{ _id: 1, tweet: 1});
let writeResult = await Tweetmodel.bulkWrite(
(await Promise.all(
tweets.map(({ _id, tweet }) => api.petition(tweet).then(result => ({ _id, result }))
)).map(({ _id, result }) =>
({ updateOne: { filter: { _id }, update: { $set: { result } } } })
)
);
} catch(e) {
console.error(e);
}
由于bulkWrite()
方法采用“数组”指令,所以几乎所有上面显示的组合都可以变化,因此您可以从上面的每个方法中处理的API调用构造该数组。 / p>