Do forEach async requests inside of a promise? - javascript

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

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 in Promise function: Can't push data into Array

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

Node.js Promise with mongoose

I have difficulty using Promise.
I want to get data from one more tables in mongodb.
but I fell in callback hell, So I tried to solve this but I couldn't.
What should I do? the result came out 'undefined'.
Many Thanks,
let mongoose = require('mongoose');
mongoose.Promise = global.Promise;
....
exports.Recommend = (id) => {
User.find({User_id: myId})
.then((result) => {
return Promise.resolve(result[0].age)
}).then(age => {
return new Promise((resolve,rejject)=>{
resolve(User.find()
.select('User_id')
.where('age').equals(age))
})
}).then((Users_id) => {
Users_id.forEach((user, idx, arr) => {
Count.find()
.select('board_id')
.where('User_id').equals(user.User_id)
.exec((err, items) => {
return new Promise((resolve,reject)=>{
resolve(
items.forEach((post, idx, arr) => {
posts.push(post.board_id)
}))
})
})
})
}).then(()=>{
console.log("posts:"+posts);
})
}
Avoid Promise.resolve, avoid using the new Promise constructor like Promise.resolve, avoid the Promise constructor antipattern, and avoid forEach, and don't forget to return the promise chain from your function:
exports.Recommend = (id) => {
return User.find({User_id: myId}).then(result => {
return User.find()
.select('User_id')
.where('age')
.equals(result[0].age));
}).then(user_ids => {
return Promise.all(user_ids.map((user, idx, arr) => {
return Count.find()
.select('board_id')
.where('User_id').equals(user.User_id)
.exec()
.then(posts => posts.map(post => post.board_id));
}));
}).then(board_ids => {
console.log("posts:"+board_ids);
})
}
You have the problem with 3rd .then, I would like to recommend you to use Promise.all function to run the parallel database query. Following example may help you
exports.Recommend = (id) => {
User.find({
User_id: myId
})
.then((result) => {
return User.find()
.select('User_id')
.where('age').equals(result[0].age)
}).then((Users_id) => {
return Promise.all(Users_id.map((user, idx, arr) => {
return Count.find()
.select('board_id')
.where('User_id').equals(user.User_id)
}));
}).then((Users_id) => {
Users_id.forEach(items => {
items.forEach(post => {
posts.push(post.board_id)
})
})
}).then(() => {
console.log("posts:" + posts);
})
}

Un-nest JavaScript Firestore Functions

How can I rewrite this code to not be nested in itself? I also need access to the values obtained in the previous functions calls.
return docRef2.doc(`/users_stripe/${context.params.userID}`).get()
.then(snapshot => {
console.log("augu", snapshot);
return stripe.customers.createSource( jsonParser(snapshot._fieldsProto.id, "stringValue"),
{ source: jsonParser(snap._fieldsProto.token, "stringValue") },
function(err, card) {
console.log("listen people", card);
return docRef2.doc(`/users_stripe/${context.params.userID}/ptypes/ptypes`)
.set(card);
});
})
I am not sure what your code is doing here. I tried to write a pseudo/sample code that might give you an idea.
My code is not checked, so might contain problem.
let fun1 = () => {
return new Promise((resolve, reject) => {
docRef2.doc('/route').get().then(snapshot => {
if( snapshot ) resolve(snapshot);
else reject(snapshot);
})
})
}
let fun2 = (snapshot) => {
return new Promies((resolve, reject)=>{
stripe.customers.createSource(jsonParser(snapshot._fieldsProto.id, "stringValue"),
{ source: jsonParser(snap._fieldsProto.token, "stringValue") },
function (err, card) {
if (err) reject(false);// or whatever you wanna return
else resolve(card);
});
})
}
async function fun(){
let res1 = await fun1(); // Should contain snapshot
let res2 = await fun2(res1); // Should contain card
return docRef2.doc(`/users_stripe/${context.params.userID}/ptypes/ptypes`)
.set(card);
}

Resolve all promises in an array of json - javascript

I need to resolve all promises in an array of json, like this:
let list = [
{
id: 1,
data: new Promise((resolve, reject) => {
resolve('Cat')
})
},
{
id: 2,
data: new Promise((resolve, reject) => {
resolve('Dog')
})
},
{
id: 3,
data: new Promise((resolve, reject) => {
resolve('Mouse')
})
}
]
I use bluebird promises.
I used a for cycle to iterate all items, I would know if there was some way more elegant.
Expected result:
[ { data: 'Cat', id: 1 },
{ data: 'Dog', id: 2 },
{ data: 'Mouse', id: 3 } ]
This should work, Promise.all and array.map like the others, but the result is correct
let list = [
{
id: 1,
data: new Promise((resolve, reject) => {
resolve('Cat')
})
},
{
id: 2,
data: new Promise((resolve, reject) => {
resolve('Dog')
})
},
{
id: 3,
data: new Promise((resolve, reject) => {
resolve('Mouse')
})
}
]
Promise.all(list.map(item => item.data.then(data => ({...item, data}))))
// that's it, that's what you want, the rest is for show now
.then(results => {
console.log(results);
});
though, that's Native Promises ... you may want to look into Promise.props and/or Promise.map in bluebird for possibly simpler code yet
It could well be as simple as
Promise.map(list, Promise.props)
.then(results => {
console.log(results);
});
tested the above, and yes, it is that simple - the snippet below has my own version of Promise.map and Promise.props - and works (at least in this case) identically to bluebird
Promise.props = obj => {
const keys = Object.keys(obj);
return Promise.all(Object.values(obj)).then(results => Object.assign({}, ...results.map((result, index) => ({[keys[index]]: result}))));
};
Promise.map = (array, fn, thisArg) => Promise.all(Array.from(array, (...args) => fn.apply(thisArg, args)));
let list = [
{
id: 1,
data: new Promise((resolve, reject) => {
resolve('Cat')
})
},
{
id: 2,
data: new Promise((resolve, reject) => {
resolve('Dog')
})
},
{
id: 3,
data: new Promise((resolve, reject) => {
resolve('Mouse')
})
}
]
Promise.map(list, Promise.props)
.then(results => {
console.log(results);
});
Resolve Promise.all() to an actual usable array of JSON.
The problem: Promise.all() resolves to an array of resolved promises. To see why this is an issue, copy and paste this into your console:
var promise1 = fetch('https://swapi.dev/api/people/2'); // C-3PO
var promise2 = fetch('https://swapi.dev/api/people/3'); // R2-D2
Promise.all([promise1, promise2])
.then(res => console.log(res)); // resolved promises - trash...
See how there's no actual usable data in those resolved promises..? On the web, you need to call another promise to convert them to text or html or, most likely, beloved json. But how can you do this efficiently?
Well, then solution to our Promise.all() issue is... more Promise.all()!!!
Example:
var promise3 = fetch('https://swapi.dev/api/people/1'); // Luke
var promise4 = fetch('https://swapi.dev/api/people/4'); // Luke's daddy
Promise.all([promise3, promise4])
.then(res => Promise.all([res[0].json(), res[1].json()]))
.then(res => console.log(res)); // real data :)
Hope this helps someone! Thanks!
With Promise.all & some JavaScript.
let list = [];
let promise = Promise.all(list.map(entry => entry.data));
promise.then((results) => {
let final = results.map((result, index) => {
return {data: result, id: list[index].id};
});
console.log(final);
});
Promise.all may be a good fit here:
Promise.all(list.map(entry => entry.data)).then((resolved) => {
// ...
});
You just need create an array of promises. you can do it with for or map
let promises = []
for(var i=0; i < list.length; i++)
promises.push(list.data)
Promise.all(promises).then(resultList => { ... }).catch(err => { ... })
or simplest may with map
Promise.all(list.map(p => p.data)).then(resultList => { ... }).catch(err => { ... })
you can use lodash lib to minimize your code
const _ = require('lodash');
Promise.all(_.map(list, 'data')).then((data)=>{
list.map((item,ind)=>{item.data = data[ind]});
console.log(list);
})

Categories