I'm trying to return the hole master/detail object to client, but detail is coming as an empty array
Like this post I've also ended with the same problem:
"Can't call res.send(data) inside the loop because res.send() can only be called once."
"But if I call res.send(array) outside of the loop, the array is still empty"
What is the right way to do it?
I'm trying not to use use asyn
var getMasterDetail = function (req, res) {
const key = "Detail";
var list = {}
list[key] = []
var modelsMaster = objModels.ObjMaster
var modelsDetail = objModels.objDetail
modelsMaster.getMasters(objModels.hdb, (e, master) => {
if (e) {
return console.log(e);
}
for (i = 0; i < master.length; i++) {
modelsDetail.getDetails(objModels.hdb, master[i].nrMaster, (e, detail) => {
if (e) {
return console.log(e);
}
for (j = 0; j < detail.length; j++) {
list[key].push(detail[j])
}
})
master[i].DetailList = list
};
res.send({ MasterDetail: master })
})
};
Thanks.
UPDATE: The answer from #Hammerbot was almost right, but,
I have not notice at the time, that I was getting the same detail for all masters.
Ex. {master:{1,2,3,4,5,6}, master{1,2,3,4,5,6}} instead of {master:{1,2,3}, master{4,5,6}}
I Have no any idea why and how to fix it. I've tried to clean the list befor the loop, and move master master[i].DetailList, creating a second Promisse for the second loop, no success.
You should use promises for that. Here is an example that should resolve your problem:
var getMasterDetail = function (req, res) {
const key = "Detail";
var list = {}
list[key] = []
var modelsMaster = objModels.ObjMaster
var modelsDetail = objModels.objDetail
modelsMaster.getMasters(objModels.hdb, (e, master) => {
if (e) {
return console.log(e);
}
const promises = []
for (i = 0; i < master.length; i++) {
const promise = new Promise(resolve => {
master[i].DetailList = list
modelsDetail.getDetails(objModels.hdb, master[i].nrMaster, (e, detail) => {
if (e) {
return console.log(e);
}
for (j = 0; j < detail.length; j++) {
list[key].push(detail[j])
}
resolve()
})
})
promises.push(promise)
}
Promise.all(promises).then(() => {
res.send({ MasterDetail: master })
})
})
};
As you can see, before the loop I initiate a promises array. Inside the loop, I create a promise by iteration that gets resolved when the callback has finished.
I push the promise into the promises Array, and at the end I use Promise.all() to wait for all the promises to get resolved before sending the result in the response.
Related
This question already has answers here:
Using async/await with a forEach loop
(33 answers)
Closed last year.
I'm trying to fetch json, check with mongodb if duplicate exist and if not, then to insert data into mongodb.
The problem is loops goes very fast, without waiting for duplicate check and insert.
What am I doing wrong here?
const fetch = require('node-fetch');
var MongoClient = require('mongodb').MongoClient;
async function fetchPhotos() {
MongoClient.connect("mongodb://localhost:27017", async function (err, dbo) {
const db = dbo.db('mydb')
if (err) throw err;
for (var i = 1; i < 100; i++) {
await fetch(`domain.com/?page=${i}`)
.then(res => res.json())
.then((response) => {
response.forEach(photo => {
console.log("checking if there is duplicate: " + photo.id);
var exists = db.collection("photos").countDocuments({"id": photo.id}, {limit: 1});
if (exists === 1) {
console.log("dupl found, next!");
} else {
db.collection("photos").insertOne(photo, function (err, res) {
if (err) throw err;
console.log("1 document inserted");
});
}
});
});
}
});
}
module.exports.fetchPhotos = fetchPhotos;
There are a few problems with your asynchronous code inside a loop.
a .forEach() loop does not wait for await. You have to use a for loop or while loop if you want it to wait for await.
You aren't using await on db.collection("photos").countDocuments({"id": photo.id}, {limit: 1});.
You aren't using await on db.collection("photos").insertOne(photo).
Do not mix plain callbacks and promises in the same flow of control. It makes it very difficult to code safely and with proper error handling.
You are missing appropriate error handling in a number of places.
You can restructure the whole thing to use the promise interface to your database and then sequence the iteration through the photos and simplify things like this:
const fetch = require('node-fetch');
var MongoClient = require('mongodb').MongoClient;
async function fetchPhotos() {
const dbo = await MongoClient.connect("mongodb://localhost:27017");
const db = dbo.db('mydb');
for (let i = 1; i < 100; i++) {
let response = await fetch(`http://example.com/?page=${i}`);
let data = await response.json();
for (let photo of data) {
console.log("checking if there is duplicate: " + photo.id);
let cnt = await db.collection("photos").countDocuments({"id": photo.id}, {limit: 1});
if (cnt > 0) {
console.log("dupl found, next!");
} else {
await db.collection("photos").insertOne(photo);
console.log("photo inserted");
}
}
}
}
// caller of fetchPhotos() gets a promise that tells you if the
// operation completed or had an error
module.exports.fetchPhotos = fetchPhotos;
If I didn't need to worry about asynchronous code, this is what it'll look like:
app.get('/user/:username/events', async (req, res) => {
// Grab all events by that user
let events = await axios.get(`${baseApiUrl}/users/${req.params.username}/events`, options).then(d => d.data)
// Loop through events
events.forEach(event => {
// If event type is A_Event
if (event.type === 'A_Event') {
// Loop through objArr array
event.payload.objArr.forEach(async obj => {
// Grab additional data through endpoint
// that lives in obj
let data = await axios.get(obj.url)
// Append data back onto obj
obj.objData = data
})
}
})
// return events obj with objData appended to each obj within
// objArr
res.json({ events })
})
But this doesnt work because it returns events before its done grabbing all the data.
I tried doing something with Promise.all but couldnt get what I needed to get.
events.map(async event => {
if (event.type === 'A_Event') {
getData(event.payload.objArr)
.then(objData => {
// This returns an array of objData that
// then needs to be added back to the specific obj
// it came from
event.payload.objArr.forEach((obj, i) => {
obj.objData = objData[i]
})
})
}
res.json({ events })
})
const getData = async objArr => {
const requests = objArr.map(async obj => {
const data = await axios.get(obj.url, options)
return {
stats: data.data.stats,
created_at: data.data.created_at
}
})
return Promise.all(requests)
}
Would really appreciate the help with this. Been stuck on this problem for a good bit.
EDIT
Tried switching to a for of loop according to this post: Using async/await with a forEach loop... No luck but I think its a step in the right direction.
events.forEach(async event => {
if (event.type === 'A_Event') {
for (let obj of event.payload.objArr) {
let objData = await axios.get(obj.url, options)
obj.objData = objData
console.log('setting value')
}
}
})
res.json({ events })
console.log('returned value')
I get returned value before I get setting value...
Promise.all(requests);
Will return a promise so you can handle it somewhere else like this:
getData(objaArr).then((result) => {
// your code
});
In your second block of code, just await for Promise.all()
return await Promise.all(requests);
You would use Promise.all as suggested in the other answer(s).
To retrieve the result you would do something like:
let [languages, currencies, countries] = await Promise.all([
this.downloadLanguages(),
this.downloadCurrencies(),
this.downloadCountries()
])
Your variables can be assigned values returned from the promises using this call structure.
Got it working. I needed to switch the initial loop to a for of. Not just the inner loop. Thanks everyone!
for (let event of events) {
if (event.type === 'A_Event') {
for ( let obj of event.payload.objArr) {
let objData = await axios.get(obj.url, options)
obj.objData = objData.data
}
}
}
You could use good old-fashioned for loops:
for (let i = 0; i < events.length; i++) {
if (events[i].type === 'A_Event') {
for (let j = 0; j < events[i].payload.objArr.length; j++) {
const data = await axios.get(events[i].payload.objArr[j].url);
events[i].payload.objArr[j].objData = data;
}
}
}
I still confused about how to use promises. I have a for loop call an asynchronous method which returns a value. I use this value to push into an array. But when I print the array it is empty. Here is what I did:
async function getLink(link) {
var browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
await page.goto(LINK)
const result = await page.evaluate( async() => {
let data = [];
const $ = window.$;
$('#gallery_01 .item').each(function(index, product) {
data.push($(product).find('a').attr('data-image'));
});
return data;
});
await browser.close();
return result;
}
var final = [];
for (var i = 0; i < 10; i++) {
var data = getLink(value[i].url).then(function(data) {
console.log(data); // urls show here
final.push(data);
});
}
Promise.all(final).then(() => {
console.log(final) // empty
})
The final show empty. What did I do wrong with Promise? Pls help!
I can't see what value is, but it looks like it's supposed to be an array of objects with a url property?
Assuming the getLink() function is okay, try this for your loop:
const final = [];
for (var i = 0; i < 10; i++) {
final.push(getLink(value[i].url));
}
Promise.all(final)
.then(data => {
console.log(data);
});
Or a slightly more compact way of accomplishing the same thing:
const promises = value.map(v => getLink(v.url));
Promise.all(promises)
.then(data => {
console.log(data);
});
Update: My bad, got a bit confused. The following code would only work without () => after the var fn
You are very close. Try this:
var final = [];
var results = []; // you need a separate array for results
for (var i = 0; i < 10; i++) {
// renamed the variable, changed 'data' to 'fn'
var fn = () => getLink(value[i].url).then(function(data) {
console.log(data); // urls show here
results.push(data);
});
final.push(fn);
}
Promise.all(final).then(() => {
console.log(results)
})
Promise.all accepts an array of promises. You have an array 'final' but seem to try to store the result of the fucntion execution as well as the function itself.
To do this correctly - first get an array of promises. Then pass them to Promise.all().
P.S. Assuming your function actually works, haven't looked at it, since the question was about promises.
I have a problem when i use q promise in loop. Result show
0
1
2
--
--
--
But result that i promise must be:
0
--
1
--
2
--
There is my code:
for(let i = 0; i < 3; i++) {
let row = planRespone[i];
let planData = {
diary_id:diaryData.id,
title:row.title
};
console.log(i);
addDiaryPlan(planData)
.then((insertId) => {
console.log("--");
})
.catch((err) => {
throw err;
})
};
And support fot it
let addDiaryDetail = (data) => {
let q = Q.defer();
Mdl.addDiaryDetail(data, function(err, result) {
if(err) q.reject(err);
else q.resolve(result);
});
return q.promise;
}
How can i use promise in this case?
As others have said, I don't see any need for Q at all. You can do this with node.js built-in functionality (util.promisify()).
And when you want to run a for loop in sequential order where you wait for an async operation to be done before going to the next iteration of the loop, then async/await is the easiest way to do things because it will pause the for loop:
const util = require('util');
Mdl.addDiaryDetailPromise = util.promisify(Mdl.addDiaryDetail);
async function someFunction() {
for (let i = 0; i < 3; i++) {
let row = planRespone[i];
let planData = {
diary_id:diaryData.id,
title:row.title
};
console.log(i);
await Mdl.addDiaryPlanPromise(planData).then(insertId => {
console.log("--");
});
};
}
// usage
someFunction().then(() => {
console.log("all done");
}).catch(err => {
console.log(err);
});
This should give you the desired sequence of output.
Note, that to use await, it has to be inside a function that is declared async and that function will always return a promise that resolves when the function is done or rejects when there's some sort of uncaught error in the function (an actual exception or a rejected await).
i have array of db like
const dbArr = ["http://localhost:5984", "http://xyz_couchdb.com:5984"]
data to insert
let data ={
_id: 324567,
name: Harry,
gerder: male
}
here is the logic i am using nano module
return new Promise((resolve, reject) => {
let res = [];
let rej = [];
let counter = 0;
for(let i = 0; i < dbArr.length ; i++){
dbArr[i].insert(data, (err, body) => {
err ? rej.push(err) : res.push(body)
if(counter === obj.dbArray.length -1){
rej.length ? reject(rej) : resolve(res)
}
counter++;
})
}
})
what can be the best possible way to achieve this using promise or async module or anything.
In the following example, we gotta use Array.map to create one promise for each element of dbArr, then we gotta wait all promises to end using Promise.all. The catch is here so we handle the errors.
function getAll(dbArr) {
return Promise.all(dbArr.map(x => x.insert(data)));
}
getAll(dbArr)
.then((rets) => {
// Handle the returns
// They are in an array
})
.catch((err) => {
// Handle the error
});
EDIT :
Ok after checking out the documentation of node-couchdb (the one I suppose you use) - I saw that the .insert() method do not return a Promise but only a callback.
So we gotta transform the method, so it will return a Promise using util.Promisify()
const {
promisify,
} = require('util');
function getAll(dbArr) {
return Promise.all(dbArr.map(x => promisify(x.insert)(data)));
}
getAll(dbArr)
.then((rets) => {
// Handle the returns
// They are in an array
})
.catch((err) => {
// Handle the error
});