您真正缺少的核心是 Mongoose API 方法也使用 "Promises",但您似乎只是在使用回调从文档或旧示例中复制。解决方案是转换为仅使用 Promises。
使用 Promise
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() 的输出,该输出正在处理来自.find() 的结果,而不是for 循环。这实际上是您尝试中最大的问题之一,因为 for 无法实际控制异步函数何时解析。另一个问题是“混合回调”,但这是我们在这里通常只使用 Promises 来解决的问题。
在Array.map() 中,我们从API 调用返回Promise,链接到实际更新文档的findOneAndUpdate()。我们还使用new: true 来实际返回修改后的文档。
Promise.all() 允许“Promise 数组”解析并返回结果数组。这些您可以看到updatedDocs。这里的另一个优点是内部方法将以“并行”而不是串行方式触发。这通常意味着更快的分辨率,尽管它需要更多的资源。
还请注意,我们使用{ _id: 1, tweet: 1 } 的“投影”仅从Model.find() 结果返回这两个字段,因为它们是其余调用中唯一使用的字段。当您不使用其他值时,这样可以节省为每个结果返回整个文档的时间。
您可以简单地从findOneAndUpdate() 返回Promise,但我只是添加了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() 将promise“链接”在一起,使它们能够按顺序解决。请注意,结果数组保持在范围内,并用附加到连接链末尾的最终 .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 调用构造该数组。