首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >使用外部API调用和findOneAndUpdate循环结果

使用外部API调用和findOneAndUpdate循环结果
EN

Stack Overflow用户
提问于 2018-06-03 19:22:06
回答 1查看 490关注 0票数 1

我正在尝试编写一个程序,从mongoose的mongoose数据库中获取文档,并使用API处理它们,然后使用处理结果编辑数据库中的每个文档。我的问题是我有问题,因为我不完全理解nodejs和异步。这是我的代码:

代码语言:javascript
复制
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中,tweets是未定义的,所以它找不到那个id。有什么解决方案吗?谢谢

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2018-06-03 19:36:22

您真正遗漏的核心是Mongoose API方法也使用"Promises",但您似乎只是使用回调从文档或旧示例中复制。这个问题的解决方案是转换为仅使用Promise。

使用Promise

代码语言:javascript
复制
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实际上无法控制异步函数何时解析。另一个问题是“混合回调”,但这是我们在这里通常只使用Promise来解决的问题。

Array.map()中,我们从API调用返回Promise,并链接到实际更新文档的findOneAndUpdate()。我们还使用new: true实际返回修改后的文档。

Promise.all()允许“承诺数组”解析和返回结果数组。这些就是你看到的updatedDocs。这里的另一个优点是内部方法将以“并行”而不是串行的方式触发。这通常意味着更快的分辨率,尽管它需要更多的资源。

另请注意,我们使用{ _id: 1, tweet: 1 }的“投影”来仅从Model.find()结果返回这两个字段,因为在其余调用中仅使用这两个字段。当您不使用其他值时,这节省了返回每个结果的整个文档的时间。

您可以简单地从findOneAndUpdate()返回Promise,但我只是添加了console.log(),以便您可以看到输出在该点触发。

正常的生产使用应该不需要它:

代码语言:javascript
复制
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()的“蓝鸟”实现,它既结合了常见的Array.map()Promise(s)实现,又具有控制并行调用的“并发性”的能力:

代码语言:javascript
复制
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调用和回写数据库的调用,则可以考虑这一点:

代码语言:javascript
复制
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()交换,因为您需要这样一种技术来“收集”在“链”中不同点解析的承诺的结果。

异步/等待

在现代环境中,从LTSV8.x开始,您实际上已经支持async/await了。这可以让你更自然地写出你的流程

代码语言:javascript
复制
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);
}

或者甚至可能按顺序处理,如果资源是问题的话:

代码语言:javascript
复制
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

最后要注意的是,如果您实际上根本不需要更新的文档作为响应,那么bulkWrite()是更好的选择,它通常允许在单个请求中在服务器上处理写入:

代码语言:javascript
复制
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语法:

代码语言:javascript
复制
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调用中构造该数组。

票数 4
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/50665831

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档