使用外部API调用和findOneAndUpdate循环结果 [英] Looping Results with an External API Call and findOneAndUpdate

查看:185
本文介绍了使用外部API调用和findOneAndUpdate循环结果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to write a program that gets the documents from a mongo database with mongoose and process them using an API and then edits each document in the database with the results of the processing. My problem is that I have problems because I don't understand completely nodejs and the asynchronous. This is my code:

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。有解决方案吗谢谢

The problem is that in the findOneAndUpdate, tweets is undefined so it can't find that id. Any solution? Thanks

推荐答案

你真正缺少的核心是Mongoose API方法也使用Promises,但您似乎只是使用文档或旧示例进行复制回调。解决方案是转换为仅使用Promises。

The core thing you are really missing is that the Mongoose API methods also use "Promises", but you seem to just be copying from documentation or old examples using callbacks. The solution to this is to convert to using Promises only.

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() 而不是 循环。这实际上是您尝试中遇到的最大问题之一,因为的无法实际控制异步函数何时解析。另一个问题是混合回调,但这就是我们通常只使用Promises来解决的问题。

Aside from the general conversion from callbacks, the main change is using Promise.all() to resolve the ouput from the Array.map() being processed on the results from .find() instead of the for loop. That is actually one of the biggest problems in your attempt, since the for cannot actually control when the async functions resolve. The other issue is "mixing callbacks", but that is what we are generally addressing here by only using Promises.

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

Within the Array.map() we return the Promise from the API call, chained to the findOneAndUpdate() which is actually updating the document. We also use new: true to actually return the modified document.

Promise.all() 允许数组Promise解析并返回一组结果。您将其视为 updatedDocs 。这里的另一个优点是内部方法将以并行而不是串联方式触发。这通常意味着更快的分辨率,但需要更多资源。

Promise.all() allows an "array of Promise" to resolve and return an array of results. These you see as updatedDocs. Another advantage here is that the inner methods will fire in "parallel" and not in series. This usually means a faster resolution, though it takes a few more resources.

另请注意,我们使用的投影{_id:1 ,推文:1} 仅从 <$返回这两个字段c $ c> Model.find() 结果,因为这些是剩余调用中使用的唯一结果。当你不使用其他值时,这可以节省返回每个结果的整个文档。

Note also that we use the "projection" of { _id: 1, tweet: 1 } to only return those two fields from the Model.find() result because those are the only ones used in the remaining calls. This saves on returning the whole document for each result there when you don't use the other values.

你只需返回 承诺 来自 findOneAndUpdate() ,但我只是在 console.log()中添加,所以你可以看到输出在那时触发。

You could simply just return the Promise from the findOneAndUpdate(), but I'm just adding in the console.log() so you can see the output is firing at that point.

正常的生产使用应该没有它:

Normal production use should do without it:

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)实现,能够控制运行并行调用的并发:

Another "tweak" could be to use the "bluebird" implementation of Promise.map(), which both combines the common Array.map() to Promise(s) implementation with the ability to control "concurrency" of running parallel calls:

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调用和写回数据库的调用,则可以考虑这一点:

An alternate to "parallel" would be executing in sequence. This might be considered if too many results causes too many API calls and calls to write back to the database:

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的结果解析在不同的点那个链。

There we can use Array.reduce() to "chain" the promises together allowing them to resolve sequentially. Note the array of results is kept in scope and swapped out with the final .then() appended to the end of the joined chain since you need such a technique to "collect" results from Promises resolving at different points in that "chain".

In来自NodeJS V8.x的现代环境实际上是当前的LTS版本并且已经有一段时间了,你实际上支持 async / await 。这可以让你更自然地写出你的流量

In modern environments as from NodeJS V8.x which is actually the current LTS release and has been for a while now, you actually have support for async/await. This allows you to more naturally write your flow

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);
}

如果资源有问题,甚至可能按顺序处理:

Or even possibly process sequentially, if resources are an issue:

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 已经隐含所以您不需要将整个查询文档作为第一个参数。

Noting also that findByIdAndUpdate() can also be used as matching the _id is already implied so you don't need a whole query document as a first argument.

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

As a final note if you don't actually need the updated documents in response at all, then bulkWrite() is the better option and allows the writes to generally process on the server in a single request:

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调用构造那个数组。

Pretty much all of the combinations shown above can be varied into this as the bulkWrite() method takes an "array" of instructions, so you can construct that array from the processed API calls out of every method above.

这篇关于使用外部API调用和findOneAndUpdate循环结果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆