Bluebird promise loop - javascript

I am trying to get all records from a DynamoDB table using promises. The problem is that DynamoDB do not return all items in one call I have to make multiple calls. If LastEvaluatedKey is not null means that I need to make another call with that key to get the remaining records. In my code I am checking that and resolving only after LastEvaluatedKey is null. But the console.log("done") is not being executed.
Here is my code:
function query(params) {
return new Promise(function(resolve, reject) {
docClient.query(params, function(err, data) {
if (err) {
reject(err)
} else {
resolve(data);
}
});
})
}
function getAllRecords(params, combinedData) {
return new Promise(function(resolve, reject) {
query(params)
.then(function(data) {
if(!combinedData) {
combinedData = [];
}
combinedData.push(data.Items);
if(data.LastEvaluatedKey) {
params.ExclusiveStartKey = data.LastEvaluatedKey;
getAllRecords(params, combinedData)
}
else {
resolve(combinedData);
}
})
})
}
getAllRecords(params)
.then(function() {
console.log('done')
})
.catch(function(error) {
console.log(error);
})
It's probably a misconception on how promises work from my part. If someone can give me an idea how to make this work. That would be great.

You've fallen prey to the explicit promise construction antipattern, in which you are manually constructing promises when you don't need to.
Generally the only time you need to use the Promise constructor is when you are converting non-Promise async code to Promise async code. You've already done that in your query() function, so you don't need to use the Promise constructor in your getAllRecords() function.
You should do this instead:
function getAllRecords(params) {
return query(params).then(function (data) {
var items = [data.Items];
if(data.LastEvaluatedKey) {
params.ExclusiveStartKey = data.LastEvaluatedKey;
return getAllRecords(params).then(function (theRest) {
return items.concat(theRest);
});
}
return items;
});
}

Related

Node.js MongoDB Utilizing promises to make MongoDB method calls

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

JavaScript promise not firing in order

I have been trying to effectively manage how I build my promises in my Express.js app.
Right now I have the following scenario: During the signup process, a user has an optional Organization Name field. If this is filled in, I need to create the Organization object and then add the _id of it to the other information that I am applying to the user. If there is no Organization name, proceed and update the basic user info.
<-- Right now the user is being updated before the organization is being created. -->
//basic info passed from the signup form
info['first_name'] = req.body.first_name;
info['last_name'] = req.body.last_name;
info['flags.new_user'] = false;
//if organization name is passed, create object and store _id in user object.
let create_organization = new Promise(function(resolve, reject) {
if (req.body.organization_name !== "") { //check if name is sent from form
Organization.create({
name: req.body.organization_name
}, function(err, result) {
console.log(result);
if (!err) {
info['local.organization'] = result._id;
resolve()
} else {
reject()
}
})
} else {
resolve()
}
});
let update_user = new Promise(function(resolve, reject) {
User.update({
_id: req.user._id
}, info, function(err, result) {
if (!err) {
console.log("Updated User!"); < --prints before Organization is created
resolve();
} else {
reject();
}
})
});
create_organization
.then(function() {
return update_user;
})
.then(function() {
res.redirect('/dash');
})
Nothing in your code waits for the first promise to settle before proceeding with starting the subsequent work. The work is started as soon as you call User.update, which is done synchronously when you call new Promise with that code in the promise executor.
Instead, wait to do that until the previous promise resolves. I'd do it by wrapping those functions in reusable promise-enabled wrappers (createOrganization and updateUser):
// Reusable promise-enabled wrappers
function createOrganization(name) {
return new Promise(function(resolve, reject) {
Organization.create({name: name}, function(err, result) {
console.log(result);
if (err) {
reject(err);
} else {
resolve(result);
}
});
});
}
function updateUser(id, info) {
return new Promise(function(resolve, reject) {
User.update({_id: id}, info, function(err, result) {
if (err) {
reject(err);
} else {
resolve();
}
})
});
}
(You may be able to use util.promisify or the promisify npm module to avoid doing that manually.)
And then:
//basic info passed from the signup form
info['first_name'] = req.body.first_name;
info['last_name'] = req.body.last_name;
info['flags.new_user'] = false;
//if organization name is passed, create object and store _id in user object.
(req.body.organization_name === "" ? Promise.resolve() : createOrganization(req.body.organization_name))
.then(function() {
return updateUser(req.user._id, info);
})
.catch(function(error) {
// handle/report error
});
(I stuck to ES5-level syntax since your code seemed to be doing so...)
See, so called 'executor function', passed into Promise constructor, is invoked immediately. That's why you essentially have a race condition here between two remote procedure calls. To solve this, make the update responsible for promise creation instead:
function updateUser(userId, userInfo) {
return new Promise(function(resolve, reject) {
User.update({_id: userId}, userInfo, function(err, result) {
if (err) {
reject(err);
}
else {
resolve(result);
}
});
});
}
... and call this function in then(). By doing this, an executor function will be called only when updateUser is invoked - and that'll be after createOrganization() finished its job.

Get object array promise, then iterate over objects

I am trying to create an array called pw_array, assign the contents of pw_subscribers to that array, then append each object in the pw_array with new key value pairs from the second promise. I am new to promises and having a lot of trouble making this work. Right now when I console.log(pw_customer) inside the second promise, inside the getCustomers function, it is returning what I want. But when I console.log(pw_array) later it is the original array.
var pw_array = [];
//first promise is working correctly
var getPaywhirlSubscribers = new Promise(function(resolve, reject) {
paywhirl.Subscriptions.getsubscribers({limit:100}, function(error, pw_subscribers) {
Promise.all(JSON.parse(pw_subscribers).forEach(function(pw_subscriber) {
pw_array.push(pw_subscriber);
}))
// console.log(pw_array);
return resolve(pw_array);
});
});
var getGatewayReferences = function(pw_array) {
return new Promise(function(resolve, reject) {
Promise.all(pw_array.forEach(function(pw_customer) {
paywhirl.Customers.getCustomer(pw_customer.customer_id, function(error, customer) {
pw_customer.phone = customer.phone;
pw_customer.address = customer.address;
pw_customer.gateway_reference = customer.gateway_reference;
// this console.log is returning what I want
// console.log(pw_customer);
});
}));
resolve(pw_array);
// console.log(pw_array);
});
};
and the promise chain...
getPaywhirlSubscribers.then(getGatewayReferences).then(function(pw_array) {
// this console.log is returning the original pw_array with pw_subscribers but not with the appended pw_customer keys
console.log(pw_array);
});
All of your code can be reduced to
var getPaywhirlSubscribers = function() {
return new Promise(function(res, rej) {
paywhirl.Subscriptions.getSubscribers({limit:100}, function(err, subs) {
if (err) {
rej(err);
} else {
res(JSON.parse(subs));
}
});
});
};
var gatewayRefs = function(promiseOfArray) {
return promiseOfArray.then(function(subs) {
return Promise.all(subs.map(function(sub) {
return new Promise(function(res, rej) {
paywhirl.Customers.getCustomer(sub.customer_id, function(err, customer) {
if (err) {
rej(err);
} else {
res(Object.assign({}, sub, customer);
}
});
});
});
});
};
gatewayRefs(getPaywhirlSubscribers()).then(function(arrayOfCustomers) {
// do something with the customer array
});
Note that you can make this even shorter/simpler if you use one of the many utilities available to automatically convert node.js-style error -first callback APIs into Promise-based ones. Search for 'promise denodeify'.
You could also conceivably pull some of the steps out into a .then chain to reduce nesting, but that's as much aesthetic as practical IMHO.

Array undefined after resolving it after for loop

I have the problem of having an undefined array which gets resolved after a for loop where it gets filled. It looks like the following:
function mainFunction() {
getUnreadMails().then(function(mailArray) {
// Do stuff with the mailArray
// Here it is undefined
})
}
function getUnreadMails() {
var mailArray = [];
return new Promise(function(resolve, reject) {
listMessages(oauth2Client).then(
(messageIDs) => {
for(var i = 0; i < messageIDs.length; i++) {
getMessage(oauth2Client, 'me', messageIDs[i]).then(function(r) {
// Array gets filled
mailArray.push(r);
}, function(error) {
reject(error);
})
}
// Array gets resolved
resolve(mailArray);
},
(error) => {
reject(error);
}
)
});
}
Both listMessages() and getMessage() returns a promise, so it is chained here. Any ideas why I am getting an undefined mailArray? My guess is that it is not filled yet when it gets resolved. Secondly I think this flow is not a good practice.
The Array is probably undefined because it is never defined; at least nowhere in your code. And your promise resolves before any iteration in your loop can resolve or better said, throw (trying to push to undefined).
Besides that. you can highly simplyfy your code by using Array#map and Promise.all.
And there's no point in catching an Error just to rethrow the very same error without doing anything else/with that error.
function getUnreadMails() {
//put this on a seperate line for readability
//converts a single messageId into a Promise of the result
const id2message = id => getMessage(oauth2Client, 'me', id);
return listMessages(oauth2Client)
//converts the resolved messageId into an Array of Promises
.then(messageIDs => messageIDs.map( id2message ))
//converts the Array of Promises into an Promise of an Array
//.then(Promise.all.bind(Promise));
.then(promises => Promise.all(promises));
//returns a Promise that resolves to that Array of values
}
or short:
function getUnreadMails() {
return listMessages(oauth2Client)
.then(messageIDs => Promise.all(messageIDs.map( id => getMessage(oauth2Client, 'me', id) )))
}
.then(Promise.all) won't work
I wanted to make the intermediate results more clear by seperating them into distinct steps/functions. But I typed too fast and didn't check it. Fixed the code.
In the short version, where does the mailArray then actually get filled/resolved
Promise.all() takes an an Array of promises and returns a single promise of the resolved values (or of the first rejection).
messageIDs.map(...) returns an Array and the surrounding Promise.all() "converts" that into a single Promise of the resolved values.
And since we return this Promise inside the Promise chain, the returned promise (listMessages(oauth2Client).then(...)) also resolves to this Array of values.
Just picking up on marvel308's answer, I think you need to create a new Promise that resolves when your other ones do. I haven't had a chance to test this, but I think this should work
function getUnreadMails() {
var mailArray = [];
return new Promise(function(resolve, reject) {
listMessages(oauth2Client).then(
(messageIDs) => {
var messages = [];
for(var i = 0; i < messageIDs.length; i++) {
messages.push(
getMessage(oauth2Client, 'me', messageIDs[i]).catch(reject)
);
}
Promise.all(messages).then(resolve);
},
(error) => {
reject(error);
}
)
});
}
This way, the resolve of your first promise gets called when all the messages have resolved
getMessage(oauth2Client, 'me', messageIDs[i]).then(function(r) {
// Array gets filled
mailArray.push(r);
}, function(error) {
reject(error);
})
is an asynchronous call
resolve(mailArray);
won't wait for it to push data and would resolve the array before hand
to resole this you should use Promise.all()
function mainFunction() {
getUnreadMails().then(function(mailArray) {
// Do stuff with the mailArray
// Here it is undefined
})
}
function getUnreadMails() {
var mailArray = [];
return listMessages(oauth2Client).then(
(messageIDs) => {
for(var i = 0; i < messageIDs.length; i++) {
mailArray.push(getMessage(oauth2Client, 'me', messageIDs[i]));
}
// Array gets resolved
return Promise.all(mailArray);
},
(error) => {
reject(error);
}
)
}
Since your getMessage function is async as well you need to wait until all your calls finish.
I would suggest using Promise.all
Here you can find more info: MDN Promise.all()
The code would look something like this:
messageIDs.map(...) returns an array of Promises
use Promise.all() to get an array with all the promises responses
resolve if values are correct reject otherwise
function mainFunction() {
getUnreadMails().then(function(mailArray) {
// Do stuff with the mailArray
// Here it is undefined
})
}
function getUnreadMails() {
return new Promise(function(resolve, reject) {
listMessages(oauth2Client).then(
(messageIDs) => {
return Promise.all(messageIDs.map(id => getMessage(oauth2Client, 'me', id)))
})
.then((messages) => resolve(messages))
.catch(error => reject(error))
});
}
One thing to keep in mind is that Promise.all() rejects if any of your promises failed
Hope this helps!
Explicit construction is an anti-pattern
I believe you can write that piece of code much shorter and, IMHO, cleaner
function mainFunction() {
getUnreadMails().then(function(mailArray) {
// Do stuff with the mailArray
// Here it is undefined
})
}
function getUnreadMails() {
return listMessages(oauth2Client)
.then((messageIDs) => Promise.all(messageIDs.map(id => getMessage(oauth2Client, 'me', id)))
}

JavaScript/Node.JS returning a promise in another promise not working

I'm a bit confused with those promise things and can't understand why I can't return a promise into another promise. My problem is the following, I have a function save :
save(Driver) {
this.exists(Driver).then(exist => {
if(exist) {
return new Promise(function (resolve, reject) {
if (exist === true) {
resolve(true);
} else if (exist === false) {
resolve(false);
} else {
reject(err);
}
});
}
});
};
As easy as it is, when I try to call that function as following :
save(driver).then(this.common.saveSuccess(res))
.catch(this.common.noSuccess(res));
I'm getting an error saying that Cannot read property 'then' of undefined and I can't understand why as I'm returning a promise.
Thank for the help
Your save function is very complicated. You don't need nested promise here, just return the result (promise) from this.exists function:
save(Driver) {
return this.exists(Driver);
};
Also, you use this function incorrectly. save function can be resolved with true or false values, so you need to validate this value in then callback and use catch callback for possible errors:
save(driver)
.then(exists => {
if (exists) {
this.common.saveSuccess(res);
} else {
this.common.noSuccess(res)
}
})
.catch(err => // process error here);

Categories