Node.js MongoDB Utilizing promises to make MongoDB method calls - javascript

EDIT
So I am creating an e-commerce website and I'm running into a few road blocks. I have since updated my code to make it cleaner and easier to read and I somewhat pinpointed where my issue stems from (though there could be more). Here is the updated code:
StoreDB.prototype.addOrder = function(order) {
console.log('addOrder');
return this.connected.then(function(db) {
console.log(order);
var orders = db.collection('orders');
var products = db.collection('products');
return insert(order, orders, db)
.then(function(updatedOrdersList) {
return obtainOrders(updatedOrdersList);
})
.then(function(orderList) {
return updateProducts(orderList, products);
})
.catch((err) => console.log('There was an issue in the promise chain\n' + err));
});
}
function insert(order, orders, db) {
return new Promise(function(resolve, reject) {
console.log('Inserting order into orders collection');
orders.insert(order);
resolve(db.collection('orders'));
});
}
function obtainOrders(orders) {
return new Promise(function(resolve, reject) {
console.log('Obtaining orders');
var orderList = orders.find({}).toArray();
resolve(orderList);
});
}
function updateProducts(orderList, products) {
return new Promise(function(resolve, reject) {
console.log('Getting the products that match the cart')
var promises = [];
var promises2 = [];
console.log('orderList', orderList);
for (var item in orderList[0].cart) {
console.log('item', item);
promises.push(products.find({$and : [{"_id" : item}, {"quantity" : {$gte: 0}}]}).toArray());
}
Promise.all(promises)
.then(() => {
console.log('Updating the cart quantity')
for (var i = 0; i < promises.length; i++) {
console.log(promises);
// console.log(productList[item], productList[item]._id, productList[item].quantity, cart);
promises2.push(products.update({"_id" : productList[item]._id}, {$set :{"quantity" : (productList[item].quantity - cart[productList[item]._id])}}));
promises2.push(products.find({}).toArray());
Promise.all(promises2)
.then(() => promises[promises2.length-1]);
}
});
});
}
It appears that when obtainOrders is called in the promise chain, it returns an empty array. I'm not too sure why it does this because in the chain where:
return insert(order, orders, db)
.then(function(updatedOrdersList) {
return obtainOrders(updatedOrdersList);
})
.then(function(orderList) {
return updateProducts(orderList, products);
})
The function call to obtainOrders from above waits for the method call to Collections.find to be completed before it is resolved (returning an array of orders). Some reason it is returning an empty array.
Any help would be greatly appreciated!

Not sure what the relationship between the first block and second is, but in the first block:
function insert(order, orders, db) {
return new Promise(function(resolve, reject) {
console.log('Inserting order into orders collection');
orders.insert(order);
resolve(db.collection('orders'));
});
}
At a glance it seems like orders.insert(order) is asynchronous, but you're not waiting for it to finish before resolving.
You could either make the function asynchronous and
await orders.insert(order);
or
orders.insert(order)
.then(() => resolve(db.collection('orders')))
But doesn't having the method Collections.insert inside a promise make that method call asynchronous?
Yes, Collections.insert is implicitly asynchronous in that it returns a Promise. I was talking about declaring it async explicitly so you can await other async calls in the function body. Note the addition of async at the start and await orders.insert().
async function insert(order, orders, db) {
return new Promise(function(resolve, reject) {
console.log('Inserting order into orders collection');
await orders.insert(order);
resolve(db.collection('orders'));
});
}
If you don't await orders.insert (either via await or .then) you're calling insert and immediately returning the db.collection call, which creates a race condition where the new order may not be inserted yet. It's possible the new order could be there but it seems unlikely, and it's not going to be there reliably.
There's also no need to create a new Promise here. db.collection already returns a Promise. Just return the one you already have:
function insert(order, orders, db) {
console.log('Inserting order into orders collection');
return orders.insert(order)
.then(() => db.collection('orders'));
}
Take a look at this sample code snippet. I've mocked out the orders.insert and db.collection methods.
// mock orders.insert
const orders = {
insert: async() => {
return new Promise((resolve) => {
// wait a second then resolve
setTimeout(resolve, 1000);
});
}
}
// mock db.collection
const db = {
collection: async() => {
return new Promise((resolve) => {
// wait a second then resolve with mock data
setTimeout(() => resolve(['foo', 'bar', 'baz']), 1000);
});
}
}
function insert(order, orders, db) {
return orders.insert(order)
.then(() => db.collection('orders'));
}
// same as above, but with async/await
async function insertAsync(order, orders, db) {
await orders.insert(order);
return await db.collection('orders');
}
function go() {
// using then
insert(null, orders, db).then(console.log);
// using async/await;
insertAsync(null, orders, db).then(console.log);
}
go();

Related

Retrieving value from Mongoose Promise as a callback

Having trouble pulling the data from a mongoose promise. I want to find a set of users from mongoose using an appropriate ID and passing the result along a promise chain.
The query is pulling the right data from the database. When I nest the query inside of a promise chain and then attempt to pass along the result, my console.log shows [Function (anonymous)]. Is there an appropriate structure for pulling the data from MongoDB using mongoose and then passing that data along a promise chain?
Promise chain:
addItem(guildId, items, timeCreated) {
return new Promise((resolve, reject) => {
// other functionality omitted for brevity
resolve(guildId, items, timeCreated);
}).then((guild, item, timeCreate) => {
findGroupUsers(guild).then((response) => {
return new Promise((resolve, reject) => {
console.log(response.userId);
resolve(response);
});
});
Mongoose query:
const findGroupUsers = async (guildId) => {
const members = await EngagementTracking.find({ guildId: `${guildId}` }).exec();
console.log(members);
return members;
};
The verified answer successfully pulls the data from the query inside of addItem. To continue and pass the result of the query along chain, I did the following:
addItem(guildId, items, timeCreated) {
return new Promise((resolve, reject) => {
//other functionality
resolve(guildId, items, timeCreated);
}).then((guild, items, timeCreate) => {
return findGroupUsers(guild).then((response) => {
return new Promise((resolve) => { resolve(response); });
});
}).then((data) =>{// other functionality //});
You function add item seem a bit strange you should do this instead:
addItem(guildId, items, timeCreated) {
return new Promise((resolve, reject) => {
// other functionality omitted for brevity
resolve(guildId, items, timeCreated);
}).then((guild, predict, timeCreate) => {
return findGroupUsers(guild).then((response) => {
console.log(response.userId);
return response;
});
you don't need to return a new promise inside a .then
also be warn that you should return your promise (you didn't return findGroupUser)
at the end you should have a addItem.then(/do something/) or an await

Iterate over array of queries and append results to object in JavaScript

I want to return results from two database queries in one object.
function route(start, end) {
return new Promise((resolve, reject) => {
const queries = routeQuery(start, end);
var empty_obj = new Array();
for (i=0; i<queries.length; i++) {
query(queries[i], (err, res) => {
if (err) {
reject('query error', err);
console.log(err);
return;
} else {
empty_obj.push(res.rows);
}});
}
console.log(empty_obj);
resolve({coords: empty_obj});
});
}
This is my code right now, the queries are working fine but for some reason, pushing each result into an empty array does not work. When I console log that empty object, it stays empty. The goal is to resolve the promise with the generated object containing the two query results. I'm using node-postgres for the queries.
Output of res is an object:
{
command: 'SELECT',
rowCount: 18,
oid: null,
rows: [
{ ...
I suggest you turn your query function into a Promise so that you can use Promise.all:
// turn the callback-style asynchronous function into a `Promise`
function queryAsPromise(arg) {
return new Promise((resolve, reject) => {
query(arg, (err, res) => {
if (err) {
console.error(err);
reject(err);
return;
}
resolve(res);
});
});
}
Then, you could do the following in your route function:
function route(start, end) {
const queries = routeQuery(start, end);
// use `Promise.all` to resolve with
// an array of results from queries
return Promise.all(
queries.map(query => queryAsPromise(query))
)
// use `Array.reduce` w/ destructing assignment
// to combine results from queries into a single array
.then(results => results.reduce(
(acc, item) => [...acc, ...item.rows],
[]
))
// return an object with the `coords` property
// that contains the final array
.then(coords => {
return { coords };
});
}
route(1, 10)
.then(result => {
// { coords: [...] }
})
.catch(error => {
// handle errors appropriately
console.error(error);
});
References:
Promise.all - MDN
Array.reduce - MDN
Destructing assignment - MDN
Hope this helps.
The issue you currently face is due to the fact that:
resolve({coords: empty_obj});
Is not inside the callback. So the promise resolves before the query callbacks are called and the rows are pushed to empty_obj. You could move this into the query callback in the following manner:
empty_obj.push(res.rows); // already present
if (empty_obj.length == queries.length) resolve({coords: empty_obj});
This would resolve the promises when all rows are pushed, but leaves you with another issue. Callbacks might not be called in order. Meaning that the resulting order might not match the queries order.
The easiest way to solve this issue is to convert each individual callback to a promise. Then use Promise.all to wait until all promises are resolved. The resulting array will have the data in the same order.
function route(start, end)
const toPromise = queryText => new Promise((resolve, reject) => {
query(queryText, (error, response) => error ? reject(error) : resolve(response));
});
return Promise.all(routeQuery(start, end).map(toPromise))
.then(responses => ({coords: responses.map(response => response.rows)}))
.catch(error => {
console.error(error);
throw error;
});
}

Getting constant value from asynchronous function

Here is my code.
const fbLoginData= {}
function _responseInfoCallbackID (error, result) {
if (error) {
fbLoginData.error="Unable to fetch data.Try again later or use other methods to sign-in"
}
else {
fbLoginData.id = result.id
fbLoginData.email=result.email
fbLoginData.name=result.name
}
}
function _responseInfoCallbackPicture (error, result) {
if (error) {
fbLoginData.error="Unable to fetch data.Try again later or use other methods to sign-in"
}
else {
fbLoginData.profile= result.data.url
}
}
export async function logInFB () {
try{
const login = await LoginManager.logInWithPermissions(["public_profile","email"])
const value = await getValue(login)
console.warn(" THE VALUE I AM SENDING ",value)
return value
}
catch(error)
{
fbLoginData.error= "Unable to fetch Data"
}
}
const getValue = (result)=> {
if (!result.isCancelled) {
return AccessToken.getCurrentAccessToken().then(
(data) => {
fbLoginData.accessToken = data.accessToken
const infoRequest = new GraphRequest(
'/me',
{
accessToken,
parameters: {
fields: {
string: 'email,name'
}
}
},
_responseInfoCallbackID,
);
const pictureRequest = new GraphRequest(
`/me/${PICTURE_PARAM}`,
{
accessToken,
},
_responseInfoCallbackPicture,
);
// Start the graph request.
new GraphRequestManager().addRequest(infoRequest).start()
new GraphRequestManager().addRequest(pictureRequest).start()
return fbLoginData
}
)
}
}
I want to have fbLoginData object to be filled with the data after having the callback functions _responseInfoCallbackID and _responseInfoCallbackPicture called. But the async function loginFB value doesn't return any other data than access token, but I want id, email, name that gets returned. I know the problem is due to asynchronicity, how can I get the fbLoginData with all the data I need? I cannot figure out the way I can do that. Any help on how to get the loginFB return the fbLoginData value with id,email,name and image that I want ?
This problem can be solved by following below steps:
Wrap the graph request's
new GraphRequestManager().addRequest(callback).start() inside a promise block
Create a global object promiseContainerand add the resolve and reject method of the above created promises to it.
Pass the above promises to Promise.all.
Resolve the promise inside of the callback function by using promiseContainer.
The Promise.all() method returns a single Promise that resolves when all of the promises passed as an iterable have resolved or when the iterable contains no promises.
const fbLoginData = {};
const promiseContainer = {};
function _responseInfoCallbackID(error, result) {
// Earlier Code
promiseContainer.infoPromise.resolve(true);
}
function _responseInfoCallbackPicture(error, result) {
// Earlier Code
promiseContainer.picturePromise.resolve(true);
}
// Earlier Code
const getValue = result => {
if (!result.isCancelled) {
return AccessToken.getCurrentAccessToken().then(data => {
// Earlier code
const infoPromise = new Promise(function(resolve, reject) {
promiseContainer['infoPromise'] = { resolve, reject };
new GraphRequestManager().addRequest(infoRequest).start();
});
const picturePromise = new Promise(function(resolve, reject) {
promiseContainer['picturePromise'] = { resolve, reject };
new GraphRequestManager().addRequest(pictureRequest).start();
});
return Promise.all([infoPromise, picturePromise]).then(() => fbLoginData);
});
}
};

Does promise resolved in n-th setTimeout cause memory leak?

I can see in Chrome task manager that the tab in which following code is running eats more and more memory, and it is not released until the promise is resolved
UPDATE
Main idea here is to use a single 'low level' method which would handle "busy" responses from the server. Other methods just pass url path with request data to it and awaiting for a valuable response.
Some anti-patterns was removed.
var counter = 1
// emulates post requests sent with ... axios
async function post (path, data) {
let response = (counter++ < 1000) ? { busy: true } : { balance: 3000 }
return Promise.resolve(response)
}
async function _call (path, data, resolve) {
let response = await post()
if (response.busy) {
setTimeout(() => {
_call(path, data, resolve)
}, 10)
throw new Error('busy')
}
resolve(response.balance)
}
async function makePayment (amount) {
return new Promise((resolve, reject) => {
_call('/payment/create', {amount}, resolve)
})
}
async function getBalance () {
return new Promise((resolve, reject) => {
_call('/balance', null, resolve)
})
}
makePayment(500)
.then(() => {
getBalance()
.then(balance => console.log('balance: ', balance))
.catch(e => console.error('some err: ', e))
})
The first time you call _call() in here:
async function getBalance () {
return new Promise((resolve, reject) => {
_call('/balance', null, resolve)
})
}
It will not call the resolve callback and it will return a rejected promise and thus the new Promise() you have in getBalance() will just do nothing initially. Remember, since _call is marked async, when you throw, that is caught and turned into a rejected promise.
When the timer fires, it will call resolve() and that will resolve the getBalance() promise, but it will not have a value and thus you don't get your balance. By the time you do eventually call resolve(response.balance), you've already called that resolve() function so the promise it belongs to is latched and won't change its value.
As others have said, there are all sorts of things wrong with this code (lots of anti-patterns). Here's a simplified version that works when I run it in node.js or in the snippet here in the answer:
function delay(t, val) {
return new Promise(resolve => {
setTimeout(resolve.bind(null, val), t);
});
}
var counter = 1;
function post() {
console.log(`counter = ${counter}`);
// modified counter value to 100 for demo purposes here
return (counter++ < 100) ? { busy: true } : { balance: 3000 };
}
function getBalance () {
async function _call() {
let response = post();
if (response.busy) {
// delay, then chain next call
await delay(10);
return _call();
} else {
return response.balance;
}
}
// start the whole process
return _call();
}
getBalance()
.then(balance => console.log('balance: ', balance))
.catch(e => console.error('some err: ', e))

how to pass parameters in promise chain

I am really stuck with passing values between promises, I wonder if I can get a help here.
I have a CustomerController calling methods in CustomerRepo
let getCustomers = customerRepo.getCustomers(req.query.offset, req.query.return);
let getProfiles = customerRepo.getProfiles(rows);
getCustomers
.then(function (rows) {
console.log(rows[0]);
})
.then(getProfiles)
I do not know how to pass rows (array of customers) to getProfiles so I can populate the customers object with profiles.
Here is the code in CustomerRepo -
var getCustomers = function (offset, _return) {
return new Promise(function (resolve, reject) {
var sqlString = 'call qsel_customers' + '(' + offset + ',' + _return + ')';
pool.getConnection(function (err, connection) {
if (err) {
return reject(err);
}
connection.query(sqlString, function (err, rows) {
if (err) {
reject(err);
}
else
resolve(rows);
connection.release();
});
connection.on('error', function (err) {
res.json({"code": 100, "status": "Error in connection database"});
});
});
});
}
and for getProfiles
var getProfiles = function (rows) {
return new Promise(function (resolve, reject) {
console.log(rows);
}
}
I get rows undefined. Also please could someone suggest how can I extract the db mysql connection code into a dbConnect.js, to avoid code repetition.
Promise denotes the computation that may fail or not at some point in time. Therefore, in order to create a Promise you do:
return new Promise(function(resolve, reject) {
// here you decide when the computation fails and when succeeds
resolve(1) // success
reject('error') // failed
})
Promise has a method (a swiss-knife, indeed) called then. It takes a function ƒ of one argument. Now, the magic of ƒ looks like this:
if ƒ returns simple value (string, number, array, etc) then it will wrap it in a new Promise and return it. The signature is then : Promise[a] -> (a -> b) -> Promise[b]. Therefore, say you have a function ∂ that returns a Promise of a number and ƒ takes a number and adds "!" to it, you and get this number, pass it to ƒ using then and end up with a new Promise:
ƒ.then(n => n + '!') // Promise[String]
if ƒ returns a Promise, then then will "extract" the value if this Promise, pass it to ∂ and return a new Promise with the result. In pseudocode:
ƒ.then(n => Promise(n + '!')) // Promise[String]
Okay, then returns a Promise. Well, let's design the code:
let getCustomers = () => Promise.resolve([ {}, {}, {} ])
// assume myApi.getProfile takes a Customer and returns a Profile
let getProfiles = (customers) => customers.map(myApi.getProfile)
getCustomers().then(getProfiles).then(console.log.bind(console));
I'm no expert in Promises but i don't think that it is good idea to put pool.getConnection inside your getCustomers promise.
It looks like another promise and should be chained rather then combined.
Getting back to your main quersion. You can do this
getCustomers
.then(function (rows) {
return getProfiles(rows);
})
.then(function() {
console.log('get profiles resolved');
});
and
var getProfiles = function (rows) {
return new Promise(function (resolve, reject) {
resolve(rows);
console.log('other log of rows:' + rows);
}
}
EDITED:
let getCustomers = customerRepo.getCustomers(req.query.offset, req.query.return);
let getProfiles = customerRepo.getProfiles;
getCustomers
.then(function (rows) {
return getProfiles(rows);
})
.then(function(rows) {
console.log('rows number ' + rows);
console.log('get profiles resolved');
);
When chaining Promises, you have to return a Promise resolved with the value you're passing from the upstream function in order to use it in the downstream function.
getCustomers
.then(function (rows) {
console.log(rows[0]);
return Promise.resolve(rows[0]);
})
.then(function(firstRow) {
console.log("The first row's id is " + firstRow.id);
return Promise.resolve(firstRow);
})
.then(getProfiles);
If you need to do asynchronous work in one of the functions, return a new Promise and invoke the resolve/reject functions as appropriate in the asynchronous callback. Promise.resolve() is a shortcut you can use if you're not doing anything asynchronously; it simply returns a Promise object resolved with the value it's passed.

Categories