Promise in Promise function: Can't push data into Array - javascript

I am using sequelizeJS. I have a Promise in Promise function. I want to make a Promise function to get data, then push this data into Array and return this Array.
I tried with this code but it's not success.
function sequelize_conversation (conversation_id, req) {
return new Promise((resolve, reject) => {
var response = []
for (let id of conversation_id) {
db.ConversationPerson.findAll({ where: {'conversation_id': id, 'user_id': { [Op.ne]: req.user.user_id }} })
.then(person => {
console.log(person) // results: { 'id': 1, 'name': 'John' }
response.push(person.dataValues)
})
}
resolve(response)
})
}
Result I get:
response = []
But I want to get:
response = [{ 'id': 1, 'name': 'John' }]
Please review my code and help me understand about Promise in Promise function. Thank you in advance!

promises imply asynchronous code ... you are resolving before any of the db.ConversationPerson.findAll has a chance to do anything, let alone complete
Your code is actually a lot simpler than you think
function sequelize_conversation (conversation_id, req) {
var promises = [];
for (let id of conversation_id) {
promises.push(
db.ConversationPerson.findAll({ where: {'conversation_id': id, 'user_id': { [Op.ne]: req.user.user_id }} })
.then(data => {
return data.dataValues
})
);
}
return Promise.all(promises);
}
Or if conversation_id is an actual Array, and adding a little bit of ES6+ goodness
const sequelize_conversation = (ids, req) => Promise.all(
ids.map(conversation_id =>
db.ConversationPerson.findAll({
where: {
conversation_id,
'user_id': {
[Op.ne]: req.user.user_id
}
}
})
.then(({dataValues}) => dataValues)
)
);

Related

ExpressJs wait till MongoDB fetch data and loop through before the output

I'm having trouble in figuring this to work, I have one table from MongoDB (collection) for comments and another collection for Users.
When the page load it looks up the comment collection and selects the relevant comments, and then it searches the user table to find the name of the user who made the comment, the data will be combined and then the response is sent.
However, the output is sent before the data is fetched from the user table and added. How can I fix this, here is my code
var output = []
const Comments = require('comments.js')
const Users = require('users.js')
function delay( ) {
return new Promise(resolve => setTimeout(resolve, 300))
}
async function delayedProcess(item) {
await delay()
Users.findById(item.user, async function(err, result) {
Object.assign(item, {name: result.name})
output.push(item)
})
}
async function processArray(array) {
const promises = array.map(delayedProcess)
await Promise.all(promises)
return res.json({data: output})
}
Comments.find({page_id: id}).sort({post_date:-1}).limit(6).then(function(data) {
processArray(data)
})
You are not returning promise from the delayedProcess function.
There you go :-
const Comments = require('comments.js')
const Users = require('users.js')
const output = []
function delayedProcess(item) {
return new Promise((resolve, reject) => {
Users.findById(item.user, function(err, result) {
if (err) return reject (err);
output.push({ ...item, name: result.name })
return resolve()
})
})
}
async function processArray(array) {
const promises = array.map(async(item) => {
await delayedProcess(item)
})
await Promise.all(promises)
return res.json({ data: output })
}
const data = await Comments.find({ page_id: id }).sort({ post_date: -1 }).limit(6)
processArray(data)
However you will always get the concatenated array. So instead taking it globally, take it as local variable
function delayedProcess(item) {
return new Promise((resolve, reject) => {
Users.findById(item.user, function(err, result) {
if (err) return reject (err);
return resolve({ ...item, name: result.name })
})
})
}
async function processArray(array) {
const output = []
const promises = array.map(async(item) => {
const it = await delayedProcess(item)
output.push(it)
})
await Promise.all(promises)
return res.json({ data: output })
}
const data = await Comments.find({ page_id: id }).sort({ post_date: -1 }).limit(6)
processArray(data)
More simplified :- Since mongodb queries itself returns promise you do not need to use new Promise syntax.
async function processArray() {
const array = await Comments.find({ page_id: id }).sort({ post_date: -1 }).limit(6)
const output = []
const promises = array.map(async(item) => {
const it = await Users.findById(item.user).lean()
item.name = it.name
output.push(item)
})
await Promise.all(promises)
return res.json({ data: output })
}

Promise.all response

Can I add Promise.resolve(value) and Promise.reject(error) at response of Promise.all().
For example,
function transferFRQ(fromUserId, fromCompanyDetails, toUserDetails, toCompanyDetails,) {
return Promise.all([
transferRFQCompany(fromCompanyDetails, toCompanyDetails),
replaceRFQCreatedBy(fromUserId, toUserDetails)
])
.then(result => Promise.resolve(result))
.catch(error => Promise.reject(error));
}
function transferRFQCompany (fromCompanyDetails, toCompanyDetails) {
return new Promise((resolve, reject) => {
Request.updateMany({
"company.id": fromCompanyDetails._id
}, {
$set: {
company: {
id: toCompanyDetails._id,
name: toCompanyDetails.name,
logo: toCompanyDetails.logo
}
}
}).then(result => resolve(result))
.catch(error => reject(error));
});
}
function replaceRFQCreatedBy (fromUserId, toUserDetails) {
return new Promise((resolve, reject) => {
Request.updateMany({
"createdBy.id": fromUserId
}, {
$set: {
createdBy: {
id: toUserDetails._id,
firstName: toUserDetails.firstMame,
lastName: toUserDetails.lastName
}
}
}).then(result => resolve(result))
.catch(error => reject(error));
});
}
I don't know whether is it correct or not, but what I need is to handle the response of transferRFQ properly because I will need to add transferRFQ in another Promise.all() to handle the error properly.
Am I doing in the wrong way? if so, How to do it correctly
Any additional advice is welcome!
I advice that you should not use unnecessary Promise wrappers, & avoid javascript hoisting.
You should try to do something like this instead
// Note that this is declared before used, to avoid javascript hoisting
function transferRFQCompany (fromCompanyDetails, toCompanyDetails) {
return Request.updateMany({ // updateMany already returns a promise right? no need to wrap it in another promise
"company.id": fromCompanyDetails._id
}, {
$set: {
company: {
id: toCompanyDetails._id,
name: toCompanyDetails.name,
logo: toCompanyDetails.logo
}
}
})
});
}
// Note that this is declared before used, to avoid javascript hoisting
function replaceRFQCreatedBy (fromUserId, toUserDetails) {
return Request.updateMany({ // updateMany already returns a promise right? no need to wrap it in another promise
"createdBy.id": fromUserId
}, {
$set: {
createdBy: {
id: toUserDetails._id,
firstName: toUserDetails.firstMame,
lastName: toUserDetails.lastName
}
}
})
}
function transferFRQ(fromUserId, fromCompanyDetails, toUserDetails, toCompanyDetails,) {
return Promise.all([
transferRFQCompany(fromCompanyDetails, toCompanyDetails),
replaceRFQCreatedBy(fromUserId, toUserDetails)
])
}
// Sample usage async/await style
(async () => {
try {
// put your params, of course
const result = await transferFRQ(...params);
// `result` is result of .then()
} catch (e) {
// `e` is result of .catch()
}
// or use it in promise-style
transferFRQ(...params)
.then(console.log)
.catch(console.error)
})()

Unable to resolve promise chain

I am trying to write a Sequelize migration script win which I am trying to update my database but it is having many asynchronous operations (database queries and then updating database with particular id)
Here is my code
return db.organizationEntries
.findAll()
.then((entries) => {
return entries.forEach(entry => {
console.log(entry);
db.organizationEntries
.findAll({
attributes: [
[
db.sequelize.fn(
'MAX',
db.sequelize.col('organizationEntries.serial_number')
),
'maximum_serial_no'
]
],
where: {
organizationId: entry.organizationId
}
})
.then(orgEntry => {
console.log(orgEntry[0].dataValues.maximum_serial_no);
let data = { serialNumber: orgEntry[0].dataValues.maximum_serial_no + 1 };
console.log(data)
//problem
db.organizationEntries.update(data, {
where: {
id: entry.id
}
})
.then((result) => {
console.log(result);
})
});
// promises.push(promise);
});
// return Promise.all(promises);
})
Actually what I am trying to do is I am trying to take the list of all orgEntries from the database and then I am finding maximum serial number for that organization_id and then updating that particular orgEntry and like this all these operations in a loop
Now the problem is coming all the things are going in order but after finding max_serial_no it is not updating the database and I am not able to resolve what I should do to make that asynchronous call work in this order
I think you can solve this in two ways:
Simultaneously Promises
In a following code I removed forEach in favor of Promise.all() and map()
The map() method create (and return) a new array with the results of calling a provided function on every element in the calling array.
Example:
let numbers = [1, 2, 3]
let doubledNumbers = numbers.map(n => n * 2)
// doubledNumbers [2, 4, 6]
The Promise.all() method take an array of Promises as argument and returns a single Promise that will be resolved when all promises will be resolved or rejected if one promise failed
Example:
let promise1 = findUserById(5)
let promise2 = findUserFriends(5)
Promise.all([promise1, promise2])
.then(values => {
// values: [user object, list of user friends]
})
Result:
db.organizationEntries.findAll()
.then(entries => {
return Promise.all(entries.map(entry => {
console.log(entry)
return db.organizationEntries.findAll({
where: {
organizationId: entry.organizationId
},
attributes: [
[
db.sequelize.fn('MAX', db.sequelize.col('organizationEntries.serial_number')),
'maximum_serial_no'
]
]
})
.then(orgEntry => {
console.log(orgEntry[0].dataValues.maximum_serial_no)
let data = { serialNumber: orgEntry[0].dataValues.maximum_serial_no + 1 }
console.log(data)
return db.organizationEntries.update(data, { where: { id: entry.id } })
})
}))
})
.then(result => {
// result: Array of updated organizationEntries
console.log(result)
})
Step by step Promises with reduce() method
The reduce() method applies a function against an accumulator and each element in the array (from left to right) to reduce it to a single value. (from MDN web docs)
Example:
let items = [{ name: 'pencil', price: 2 }, { name: 'book', price: 10 }]
let total = items.reduce((total, item) => total += item.price, 0)
// total: 12
Result:
db.organizationEntries.findAll()
.then(entries => {
return entries.reduce((previousPromise, entry) => {
console.log(entry)
return previousPromise
.then(_ => {
return db.organizationEntries.findAll({
where: {
organizationId: entry.organizationId
},
attributes: [
[
db.sequelize.fn('MAX', db.sequelize.col('organizationEntries.serial_number')),
'maximum_serial_no'
]
]
})
})
.then(orgEntry => {
console.log(orgEntry[0].dataValues.maximum_serial_no)
let data = { serialNumber: orgEntry[0].dataValues.maximum_serial_no + 1 }
console.log(data)
return db.organizationEntries.update(data, { where: { id: entry.id } })
})
.then(updatedEntry => {
console.log(updatedEntry)
})
}, Promise.resolve())
})
.then(result => {
// result: Last updated organization entry
console.log('finished')
})

Promise.all does not wait for all the promise to finish firestore data read

I have below data structure that will be added to the firestore collection as document
{
".......": "......." //few more properties
"productInfo": {
"0": {
"code": "SC05",
"price": 400,
"productId": "asUsd1HPEPOo2itiKxdash",
".......": "......." //few more properties
"addOns": [{
"aid": "4casedasgdfdgfas",
"price": "50",
"......": "....." //few more properties
}]
}
}
}
So I will be having array of products object and each product will have array of addon object.
I am trying to retrieve price from db before saving it as document and hence I've written .onCreate function for that particular table. To retrieve price on the product level and addon level, I am having 2 promises as below:
const productPromises = [];
_.map(products, (product) => {
productPromises.push(new Promise((resolve, reject) => {
db.doc(`product/${product.productId}`).get().then(docData => {
return resolve({
productId: product.productId,
halfkgprice: docData.data().halfkgprice,
price: docData.data().price
});
}).catch(reason => {
return reject(reason);
});
}));
});
const addonPromise = [];
_.map(products, (product) => {
addonPromise.push(new Promise((resolve, reject) => {
if (product.addOns !== undefined && product.addOns !== null) {
return _.map(product.addOns, (addon) => {
return db.doc(`addons/${addon.aid}`).get().then((addonData) => {
return resolve({
price: addonData.data().price
});
}).catch(reason => {
return reject(reason);
})
});
} else {
return resolve();
}
}));
});
and then I do have
Promise.all([productPromises, addonPromise]).then(result => {
//do something with result
});
But this will not wait any of the promise to resolve. When I log the result I get as below.
console.log(result) will show [ [ Promise { <pending> } ], [ Promise { <pending> } ] ] and doesn't wait for them to resolve
My question is, why is not waiting for all the promises to resolve? Is there any problem with the way I am returning data from firestore get query?
productPromises and addonPromise are arrays of promises, so the array you pass to Promise.all is an array of arrays, not an array of promises - try
Promise.all([...productPromises, ...addonPromise]).then
Also, avoid the Promise constructor anti-pattern
_.map(products, (product) => {
productPromises.push(
db.doc(`product/${product.productId}`).get().then(docData => {
return ({
productId: product.productId,
halfkgprice: docData.data().halfkgprice,
price: docData.data().price
});
})
);
});
You need to concat 2 arrays to make it work as expected
Promise.all(productPromises.concat(addonPromise)).then(result => {
//do something with result
});
map builds an array of returned values, so you can just write:
const productPromises = _.map(products, product => db.doc(`product/${product.productId}`).get()
.then(docData => ({
productId: product.productId,
halfkgprice: docData.data().halfkgprice,
price: docData.data().price
})
);

Do forEach async requests inside of a promise?

Need help related to promises. Please refer to details below, topic is more theoretical, I don't understand what flow should I use:
We have async function getDataFromUri(), which returns data, which gets filtered and saved to arr of objects, lets name it res;
For each campaign(object) inside of array res I want send async request which will store products images of campaign to object. As result I should have ONE array res where ALL filtered data (campaign name, campaign images) is stored;
I need smth like:
[{
name: "Somename",
images: ['uri', 'uri', 'uri', 'uri', 'uri', 'uri']
},{
name: "Somename",
images: ['uri', 'uri', 'uri', 'uri', 'uri', 'uri']
}
]
Do some other actions with RES array.
This function gets campaigns:
function getDataFromUri(uri) {
return new Promise((resolve, reject) => {
request.get(uri, (err, res, body) => {
if(err || res.statusCode !== 200 ) {
reject(handleErr(err));
} else {
resolve(body);
}
});
});
}
This function gets images of campaign:
function getProductsOfCampaign(id) {
var productsImagesLinks = [];
return new Promise((resolve, reject) => {
getDataFromUri(`SOME_URI/${id}.json`)
.then((json) => {
var productsList = JSON.parse(json).products;
resolve (productsList.map((product) => product.imgSrc));
}).catch((e) => {
throw new Error(e);
})
});
}
Here I met problem:
getDataFromUri(someLink) //Get campaings;
.then((result) => {
//NOT WORKING FOREACH
result.forEach((item, i) => {
item.images = getProductsOfCampaign(item.id);
})
return result;
})
.then((result) => {
//Do something else to array with images;
});
How can I force next after forEach .then() expression to wait all images URLs to be saved?
I tried Promise.all(), but seems have lack of knowledge on how to implement it correct way.
I will really appreciate if you help me resolve this case. Thank you.
Observe that:
item in forEach is a copy.
getProductsOfCampaign returns a Promise.
The web is a best-effort service.
Do this:
getDataFromUri(someLink) // Get campaigns
.then(result => {
var promises = result.map(item =>
getProductsOfCampaign(item.id)
.then(products => {
item.images = products;
return item;
})
// 3: Best-effort service
.catch(() => {})
);
return Promise.all(promises);
}).then(items => {
console.log(items);
// Do something else to array of items with images
});
Other readers can test for correctness with this:
function getDataFromUri(someLink) {
return new Promise((resolve) => {
setTimeout(resolve, 1000, [{id: 1}, {id: 2}]);
})
}
function getProductsOfCampaign(id) {
return new Promise((resolve) => {
setTimeout(resolve, 1000, id * id);
})
}
var someLink = '';
Thanks to Benjamin Gruenbaum for suggesting that .catch(() => {}) can be used with Promise.all for a best-effort service.
let campaigns = null;
getDataFromUri(someLink) //Get campaings;
.then((result) => {
campaigns = result;
let pImages = []
result.forEach((item, i) => {
pImages.push(getProductsOfCampaign(item.id));
});
return Promise.all(pImages);
})
.then((images) => {
campaigns.forEach((campaign, index) => {
campaign.images = images[index];
});
// ... Do something else to array with images;
});

Categories