Mongodb iterate a collection of in a synchronous way using promises - node.js

I have a method (see code example) and I'm trying to return a promises list to parent method. My problem is when pushing promises in my promiseList array.
getDistance is a method returning a Promise. If I do .then() in getDistance method,everything is OK. Nevertheless, if I try to push the promise into my array, when I do .then in parent method, it is empty.
I think it may happen due to each loop is asynchronous...
var promiseList = [];
res = db.collection.find query statement...
res.each(function(err, doc) {
if (doc!==null) {
promiseList.push(
getDistance(orgLat, orgLon, destLat, desLon)
);
}
});
return Promise.all(promTimeList);
I'm using MongoDB as database, and NodeJS in server side, with mongodb as driver to connecto to my database and Bluebird as library to implement promises.
Thanks in advance!

I think you are correct in that the issue is due to the fact that each is asynchronous. You can use defer to wrap callback APIs (like each) into promises. It would work something like this:
res = db.collection.find query statement...
processResponse(res)
.then(function(promiseList) {
// execute all the promises
return Promise.all(promiseList);
}, function (err) {
// handle error
});
function processResponse(res) {
var deferred = Promise.pending();
var promiseList = [];
res.each(function(err, doc) {
if (err) {
// error, abort
deferred.reject(err);
return;
}
else if (doc != null) {
promiseList.push(
getDistance(orgLat, orgLon, destLat, desLon)
);
}
else {
// finished looping through all results, return the promise list
deferred.resolve(promiseList);
}
});
return deferred.promise;
}
See more on defer with bluebird at the following link (look for "So when should deferred be used?"):
https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns
Update: According to this post:
Define empty Bluebird promise like in Q
It looks like there is a Promise constructor that may be the preferred way to do this. The processResponse method would look like this:
function processResponse(res) {
return new Promise(function(resolve, reject) {
var promiseList = [];
res.each(function(err, doc) {
if (err) {
// error, abort
reject(err);
return;
}
else if (doc != null) {
promiseList.push(
getDistance(orgLat, orgLon, destLat, desLon)
);
}
else {
// finished looping through all results, return the promise list
resolve(promiseList);
}
});
});
}
Thanks to #Bergi. Please correct me if I am wrong. I am more familiar with the Q library (https://github.com/kriskowal/q) than bluebird.

Iterate the collection syncrhonously by using await with cursor.next() in a while loop:
var cursor = db.collection.find({});
while ((doc = await cursor.next()) != null) {
promiseList.push(
getDistance(doc.orgLat, doc.orgLon, doc.destLat, doc.desLon);
);
}

Related

node async function - get the result to the outside function

I have the following async function and want to have the value data.
I tried with Promises and Async/Awaits. I always miss something. Would be happy for some help. Thanks!
Here the async function:
ad.getData("ID1", function (err, res) {
let data = 11111;
if ((res!= null) && (res.val != null)){
data = res.val;
} else {
// store data
}
});
and I tried:
async function getData(id){
return await ad.getData(id, function (err, res) {
let data = 11111;
if ((res!= null) && (res.val != null)){
data = res.val;
} else {
// store data
}
return data;
})
}
let data = await getData("Id1");
console.log(data);
This is an asynchronous operation, and the code is written in callback style.
ad.getData("ID1", function (err, res) { // the function is a callback
let data = 11111;
if ((res!= null) && (res.val != null)){
data = res.val;
} else {
// store data
}
});
In order to get the result outside the function, or in general, write asynchronous code that look like a synchronous one, we can wrap callback style code in a Promise, like this :
function getDataAsync() {
return new Promise ((resolve, reject) => {
// callback style code is wrapped in the Promise
ad.getData("ID1", function (err, res) {
let data = 11111;
if ((res!= null) && (res.val != null)){
data = res.val;
} else {
// store data
}
resolve(data); // it means, when the Promise is resolved, it will give you the data
});
And you can get the output of getDataAsync like this :
let data = await getDataAsync("Id1");
// we use "await" keyword to wait for the Promise is resolve, data is the variable in the resolve
console.log(data);
You should use Promise instead of callback, and async/await will help you write Promise code easier.
Please take a deep look at https://scotch.io/courses/10-need-to-know-javascript-concepts/callbacks-promises-and-async. When you understand the concept, it's literally easy.

async await not working with callback node (without promise)

hello guys i got confused why this not working
here is my connection js file
function getConnection(callback) {
initPool()
mysql_pool.getConnection((err, conn) => {
if (err) {
return callback(err);
}
return callback(err, conn);
});
}
function query(queryString, callback) {
getConnection((err, connection2) => {
if (connection2 != undefined) {
if (err) {
connection2.destroy();
return callback(createDataResponseObject(err, null))
}
connection2.query(queryString, function (err, rows) {
connection2.destroy();
if (!err) {
return callback(createDataResponseObject(err, rows))
}
else {
return callback(createDataResponseObject(err, null))
}
});
}
});
}
function createDataResponseObject(error, results) {
if (error) {
logger.info(error);
}
return {
error: error,
results: results === undefined ? null : results === null ? null : results
}
}
now when i acquire this connection js in my router.js below is sample code
var conn=require("./connection")
router.post('/test',async function(res,req){
var query ="select * from users"
let x= await conn.query(result);
console.log(x)
});
in connection js file i haven't use promise then why async not working in router i.e it should still work because i m using callback can anyone tell me what i m missing or what i m doing wrong. when i tried it with return promise in connection.js file it working. i know async return promise
You can only await a promise. conn.query doesn't return a promise. It has no return statement: It returns undefined.
You need to promisify your database functions.
Async/await only works with promises, it’s a syntactic sugar on top of it. I.e. you can’t use the syntax with callbacks.
Check out this link: https://javascript.info/async-await
Async only works with promises, to get things working you have to first convert all of your function to return promises. You can use Promise.promisify() to convert all callback style function to return a promise. Once it is done you can use these function with async-await.

NodeJS sync flow

Hello i have been using a library called deasync it allows me to get things done in a cleaner way, i have a method like this,
syncGetMany: function(model, query){
var ret;
setTimeout(function(){
model.find(query, (error, body) => {
if(body){
//Awesome got the data
ret= body
}
else{
//What a cruel world
// No data, fall back to an empty array
ret = [];
}
});
},0);
while(ret === undefined) {
require('deasync').sleep(0);
}
//returns an empty array or the real data
return ret;
},
Then i simply call it like this.
var data = syncGetMany(MongooseModel, queryObj);
// the rest of my code
QUESTION: is there a way to get this done using ES6, or any similar library.
PS: Not duplicate as other questions are not relevant to my context
simplest way to get this code more cleaner is to use async/await, it's available in node.js version => 7.0. If I think well your model.find method returns promise.
async syncGetMany (model, query) {
let ret;
ret = await model.find(query);
//ret is keeping body from response now, if error occured error is throwed as promise exception
//Could do some sync ops on ret variable
return ret;
}
When you are using async/await you should put your method execution into try/catch. But you can also catch errors inside syncGetMany method, probably it should looks like this:
async syncGetMany(model, query) {
let ret;
try {
ret = await model.find(query);
return ret;
} catch(err) {
console.error(err);
return []; //empty array or any error string/object
}
}
And your execution looks like you wrote with additional await operator (using es6 provide let/const operators for var operator replacement)
let data = await syncGetMany(MongooseModel, queryObj);
Article with async/await explanation:
https://blog.risingstack.com/async-await-node-js-7-nightly/
If you don't want to use Node v7.0 you should code something like this using promises and generators.
https://medium.com/#rdsubhas/es6-from-callbacks-to-promises-to-generators-87f1c0cd8f2e
I hope I helped.
Update
So when your mongoose instance doesn't supports promises there are three options (depends I think on node versions). In my opinion promises are cleaner way to make asynchronous request so I suggest using them.
First option (native promises):
syncGetMany(model, query) {
return new Promise((resolve, reject) => {
model.find(query, (err, res) => {
if(err) {
return reject(err);
}
resolve(res);
});
});
}
Second option (bluebird library, added Async postfix to all methods):
const mongoose = require( 'mongoose' );
const Promise = require('bluebird');
Promise.promisifyAll( mongoose );
async syncGetMany(model, query) {
let ret;
try {
ret = await model.findAsync(query);
return ret;
} catch(err) {
console.error(err);
return []; //empty array or any error string/object
}
}
Third option (Node version => 8.0):
Using native promisify on function:
https://nodejs.org/dist/latest-v8.x/docs/api/util.html#util_util_promisify_original
On our synchronous part (with async operator before function construction):
let data = await syncGetMany(MongooseModel, queryObj);
You can write and execute logic as if it was synchronous using nsynjs:
function synhronousCode(model, query) {
function syncGetMany(model, query){
return model.find(query).data; // data will contain result of resolved promise at this point
};
var data = syncGetMany(model, query);
// do stuff with data
}
var nsynjs = require('nsynjs');
....
nsynjs.run(synhronousCode,{},model, query, function(){
console.log('synhronousCode done');
});
nsynjs will evaluate code in synhronousCode in sequential manner: if some function returns promise (as model.find does), nsynjs will pause execution and wait until promise is resolved, assigns result to data property of returned value, and then continue execution.

NodeJs Mongoose How can I get out a data from "find().then" in a "find().then"?

Sorry for my Title, I don't know what can I put.
Can you help me please, I would like to print data from a "then" in a "then" ?
Thank you
models.book.find()
.then( function (content) {
var i = 0;
while (content[i]) {
models.author.findOne({"_id": content[i].author_id}, function(err, data) {
console.log(data); //here, it' good
content[i] = data;
MY_DATA = content;
return MY_DATA;
});
i++;
};
})
.then(function (result) {
console.log(result); // here I would like to print MY_DATA
});
There are a number of problems with your code, and I don't think it's behaving as you're expecting it to.
Chaining Promises
In order to effectively chain promises how you're expecting, each promise callback needs to return another promise. Here's an example with yours changed around a bit.
var promise = models.book.find().exec(); // This returns a promise
// Let's hook into the promise returned from
var promise2 = promise.then( function (books) {
// Let's only get the author for the first book for simplicity sake
return models.author.findOne({ "_id": books[0].author_id }).exec();
});
promise2.then( function (author) {
// Do something with the author
});
In your example, you're not returning anything with your callback (return MY_DATA is returning within the models.author.findOne callback, so nothing happens), so it's not behaving as you're expecting it to.
model.author.findOne is asynchronous
model.author.findOne is asynchronous, so you can't expect to call it multiple times in the callback without handling them asynchronously.
// This code will return an empty array
models.book.find( function (err, books) {
var i = 0, results = [];
while (books[i]) {
models.author.findOne({ "_id": books[i].author_id}, function (err, data) {
// This will get called long after results is returned
results.push(data);
});
i++;
};
return results; // Returns an empty array
});
Handling multiple promises
Mongoose uses mpromise, and I don't see a method to handle multiple promises together, but here's a way your case could be done.
var Promise = require('mpromise');
models.book.find().exec()
.then( function (books) {
var i = 0,
count = 0,
promise = new Promise(),
results = [];
while (books[i]) {
models.author.findOne({ "_id": books[i].author_id }, function (err, author) {
results.push(author);
count++;
// Keep doing this until you get to the last one
if (count === books.length) {
// Fulfill the promise to get to the next then
return promise.fulfill(results);
}
return;
});
}
return promise;
})
.then( function (results) {
// Do something with results
});
I don't know if this will work exactly like it is, but it should give you an idea of what needs to be done.

node.js deferred misunderstanding

I`m learning q.js and trying to query 3 collections simultaneously with its help (avoiding callback hell):
var Q = require('q')
var deferred = Q.defer();
users() is a wrapper of db.collection.find()
var users = function (){
Auth.listUsers({role:'user'}, call)
return deferred.promise
}
call() is a shorthand for exporting promises
var call = function (err,data){
if (err) {
deferred.reject(err);
} else {
deferred.resolve(data);
}
}
loop() is main loop, which gets cursor and loops through entries
var loop = function (result) {
var list = []
var promises = [];
result.each(function (err,data){
if (err) {
deferred.reject(err);
} else {
deferred.resolve(data);
promises.push(deferred.promise)
console.log('promises_:', promises) // <- internal
}
})
console.log('promises:', promises) // <- external
return Q.all(promises)
}
code:
users()
.then(loop)
.then(function(ful){
console.log('ful:', ful); // <- here should come the queries to other collections
})
result of console logging at the end:
promises: [] //external is empty
ful: []
promises_: [ [object Object] ] // internal is being filled
promises_: [ [object Object], [object Object] ]
As you can see, the callback of .each is executed later than pushing promises to array.
I believe it can be made by using result.toArray() instead of .each, but how can it be done with the help of .each loop?
result is a cursor, returned by mongodb driver after db.collection.find() call (http://mongodb.github.io/node-mongodb-native/api-generated/collection.html#find).
var deferred = Q.defer();
…
deferred.resolve(…);
…
deferred.resolve(…);
call() is a shorthand for exporting promises
That will not work! You need to create a new deferred for each promise that you want. However, you shouldn't use Deferreds anyway! Instead, use the many Node callback helper functions.
As you can see, the callback of .each is executed later than pushing promises to array. I believe it can be made by using result.toArray() instead of .each, but how can it be done with the help of .each loop?
It cannot, unless you know beforehand how often each will be called and how many promises will need to be created. Q.all is a bit useless in here, since the promises are not created at once and execute their tasks in parallel - instead, they are a stream.
You really should use toArray here, to get a single callback with which you resolve the promise.
Well, there is a way, but it's ugly and less efficient than toArray. You can have one deferred that will always wait, and is only resolved with a promise for the rest of the stream.
function loop(result) {
var deferred = Q.defer();
result.each(function (err,data){
if (err) {
deferred.reject(err);
} else if (data == null) {
deferred.resolve([]); // end of the stream
} else {
var nextDeferred = Q.defer();
deferred.resolve(nextDeferred.promise.then(function(rest) {
return [data].concat(rest);
}));
deferred = nextDeferred;
}
})
return deferred.promise;
}

Resources