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);
})
}
})
The problem is that in the findOneAndUpdate, tweets is undefined so it can't find that id. Any solution? Thanks
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.
Working with Promises
Aside from the general conversion from callbacks, the main change is using
Promise.all()
to resolve the ouput from theArray.map()
being processed on the results from.find()
instead of thefor
loop. That is actually one of the biggest problems in your attempt, since thefor
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.Within the
Array.map()
we return thePromise
from the API call, chained to thefindOneAndUpdate()
which is actually updating the document. We also usenew: true
to actually return the modified document.Promise.all()
allows an "array of Promise" to resolve and return an array of results. These you see asupdatedDocs
. 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.Note also that we use the "projection" of
{ _id: 1, tweet: 1 }
to only return those two fields from theModel.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.You could simply just return the
Promise
from thefindOneAndUpdate()
, but I'm just adding in theconsole.log()
so you can see the output is firing at that point.Normal production use should do without it:
Another "tweak" could be to use the "bluebird" implementation of
Promise.map()
, which both combines the commonArray.map()
toPromise
(s) implementation with the ability to control "concurrency" of running parallel calls: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:
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".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 flowOr even possibly process sequentially, if resources are an issue:
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:Or via
async/await
syntax: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.