How do I wait for a promise in loop to finish before do some other stuff? - node.js

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.

Related

How to await http requests in express

I'm trying to get a server to make a series of API calls according to a list of parameters obtained from a form. However, with the async http get method or the npm got library I cannot get my code to await for the responses of the API calls before it tries to render the response. My code looks like this:
router.post('/getPolicies', async function(req, res, next){
let issnList = req.body.issn_list
issnList = issnList.split(/\r?\n/)
await getPolicies(issnList);
res.render("policy", {data:policies})
});
async function getPolicies(issns){
for (let i = 0; i<issns.length; i++){
url = "some_url"+ issns[i]
const {data} = await got.get(url).json();
constructDataSet(issns[i], data)
}
}
function constructDataSet (issn, data){
//...
//get some information out of a json and construct a new json with data in needed format
}
The error I get is "Cannot read properties of undefined" because it's trying to read properties of the "data" json when it hasn't gotten it yet.
I think its because you are traversing the list synchronously when it should be asynchronous. Below are two diff implementation, the for await loop believe was made available in es6 and the second would be an alternative implementation.
router.post('/getPolicies', async function(req, res, next){
let issnList = req.body.issn_list
issnList = issnList.split(/\r?\n/)
await getPolicies(issnList);
res.render("policy", {data:policies})
});
// Prefer
async function getPolicies(issns){
for await (const issns of issns){
let url = "some_url" + issn;
const {data} = await got.get(url).json();
constructDataSet(issns, data)
}
}
async function getPoliciesV2(issns) {
await forEach(issns, (issn) => {
let url = "some_url" + issn;
const {data} = await got.get(url).json();
constructDataSet(issns, data);
});
}
async function forEach(data, callback) {
for(let i = 0; i < data.length; i++) {
callback(data[i]);
}
}
function constructDataSet (issn, data){
//...
//get some information out of a json and construct a new json with data in needed format
}

Asynchronous function not waiting for Promise

I've written following asynchronious node.js function, which accesses to my database via Mongoose and thus is an async function):
function getWindSpeed(householdID){
return new Promise(async function (resolve, _){
const household = await Household.findById(householdID)
resolve(stoch.norm(household.windSimulation.mu, household.windSimulation.sigma, 1))
})
}
In the other hand, I have following function, which is also async because both accesses to the database and uses the previous function for each element in the database:
async function getMeanWindSpeed() {
return new Promise(async function (resolve, reject){
let numberOfHouseholds = 0
let averageWind = 0.0
console.log('Begin')
await Household.find({}, async function (error, households) {
if(error){
reject(error)
}else{
numberOfHouseholds = households.length
for(let i = 0; i < households.length; i++){
const speed = await wind.getWindSpeed(households[i].id)
console.log(speed)
averageWind += speed
}
}
})
averageWind = averageWind / numberOfHouseholds
console.log('Finish')
resolve(averageWind)
})
}
As you can see, I iterate over all the elements in the collection and apply the getWindSpeed() function, however it doesn't wait for its completion, as I get the following trace based on the console.log(...) debug messaged:
Begin
Finish
12.2322
23.1123
(more speeds)
...
Some more information that may be usefuk:
I'm awaiting for the result of getMeanWindSpeed() in another async function
I tried to return one hardcoded value for each element in the database (instead of calling getWindSpeed() and it worked fined, so I guess the problem is in that function.
Thanks in advance
If we don't pass a callback to .find() we'll get a promise returned, this makes the code a lot easier to read.
We could further simplify the function getMeanWindspeed since it becomes a simple wrapper for getAverageWindSpeed();
For example:
async function getAverageWindspeed() {
let numberOfHouseholds = 0
let averageWind = 0.0
let households = await Household.find({});
numberOfHouseholds = households.length
for(let i = 0; i < households.length; i++){
const speed = await wind.getWindSpeed(households[i].id)
console.log(speed)
averageWind += speed
}
return averageWind / numberOfHouseholds;
}
async function getMeanWindSpeed() {
console.log('Begin')
let averageWind = await getAverageWindspeed();
console.log('Finish')
return averageWind;
}
Why you are mixing await with promise. It is bad practice. If you can do same thing using await and async. See the below example.
const fakeDelay = () => new Promise(r => {
setTimeout(() => r("data"), 1000);
})
const Household = {
findById: () => fakeDelay()
}
async function getWindSpeed(householdID){
const household = await Household.findById(householdID)
console.log()
//stoch.norm(household.windSimulation.mu, household.windSimulation.sigma, 1)
return household;
}
const main = async () =>{
getWindSpeed().then(console.log)
}
main()
// notice
async function getWindSpeed
will be by default promise

forEach function not running in tcp event

I am trying to use the forEach() function in the node js net library in my tcp event but it is completely failing to run what is going wrong
I have made a async function to replace forEach() but it still wont work even tho i know the function is being called
async function asyncForEach(array, callback) {
console.log('async function')
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
nothing happens except the expected console.logs this is how i am using the function
sock.on('data',function(data){
var data = Buffer.from(data).toString();
var arg = data.split(',');
var event = arg[0];
if(event == 'stdout'){
console.log('stdout')
asyncForEach(controlClients, async (num) => {
await waitFor(50);
console.log(num);
});
}
});
i am expecting the return each row of the array here so i can do something for a selected controlClient any help?
It's a little hard to figure out exactly what your full environment is. When I run this code:
const waitFor = (ms) => new Promise(r => setTimeout(r, ms));
async function asyncForEach(array, callback) {
console.log('async function')
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
let controlClients = [1,2,3,4];
function test() {
console.log('stdout')
return asyncForEach(controlClients, async (num) => {
await waitFor(50);
console.log(num);
});
}
test().then(() => {
console.log("done");
}).catch(err => {
console.log(err);
});
I get this output:
stdout
async function
1
2
3
4
done
Hopefully you can take this as a starting point and figure out what is going wrong in your particular environment. Note, you should always have a .catch() on pretty much any operation that involves a promise so you can see if there are any errors in your promise chain.
And, since you don't show your socket code so I can't reproduce that part of it, I simplified to just run the rest of the code once in a small test app.

Node API - res.send() from 2nd loop

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.

Using promises to control flow is not working properly

I am trying to control the flow of the execution in my code below, meaning I want it to be serial.
I am reading and updating data from and to my DB, and ofc I want that to happen in the correct order. Below is the function I am calling my DB from, the queries functions are wrapped in callbacks.
I am pretty new to promises so perhaps the error might be something silly I am overlooking. If you need anything to ask please do so.
function my_function(array, array2)
{
var array3 = [];
return Promise.resolve(true)
.then(function()
{
console.log("1")
for(var i=0; i< array.length; i++)
{
get(array[i], function(results){
console.log("2")
array3.push(..);
});
}
return array3;
}).then(function()
{
console.log("3")
for(var i=0; i< array2.length; i+=2)
{
//...
get(array2[i], function(results){
console.log("4")
return array3.push(...);
});
}
return array3;
}).then(function(array3)
{
console.log("5")
for(var i=0; i<array3.length; i++)
{
get(array3[i], function(results){
console.log("6")
update(.., function(callb_result){
return;
});
});
}
});
}
And here is the way I am calling the queries.
function get(array, callback)
{
db.get(`SELECT .. FROM .. WHERE ..;`, function(error, row) {
...
return callback(something);
});
}
function update(.., callback)
{
db.run(`UPDATE .. SET ...`);
return callback("updated"); //I dont want to return anything
}
Whats printed in the log
1
3
5
2
4
6
I was thinking perhaps the way I ma calling the queries is async and that's messing up everything.
You're using for loops to run asynchronous tasks and return an array that is modified by them. But because they are asynchronous the return happens before they are finished. Instead you can create an array of promises where each promise is one of the asynchronous tasks that resolves once the task is done. To wait until every task is done you can call Promise.all with the array of promises, which returns a promise that resolves with an array of the resolved results.
For the first .then you can use Array.prototype.map to easily create an array of promises. Each item in the array needs to return a new Promise that resolves with the result from the callback of get.
.then(function() {
console.log("1");
const promiseArray = array.map(function(item) {
return new Promise(function(resolve) {
get(item, function(result) {
console.log("2");
resolve(result);
});
});
});
return Promise.all(promiseArray);
})
As you return Promise.all the next .then call be executed once all the promises in the promiseArray are fulfilled. It will receive the array of results as the first parameter to the function. That means you can use them there. The second .then is similar to the first one, except that you don't want to call get on every item. In this case map is not applicable, so the for loop will just create a promise and add it to the array of promises. Before you have used array3 to store the results that you want to update, but with promises you don't really need that. In this case you can simply concat the results of both arrays.
.then(function(resultsArray) {
console.log("3");
const promiseArray2 = [];
for (var i = 0; i < array2.length; i += 2) {
const promise = new Promise(function(resolve) {
get(array2[i], function(results) {
console.log("4");
resolve(results);
});
});
promiseArray2.push(promise);
}
// Wait for all promises to be resolved
// Then concatenate both arrays of results
return Promise.all(promiseArray2).then(function(resultsArray2) {
return resultsArray.concat(resultsArray2);
});
})
This returns a promise that resolves with the concatenated array, so you will have all the results (from both .then calls) as an array, which is passed to the next .then function. In the third and final .then you simply call update on each element of the array. You don't need to call get again, as you've already done this and you passed on the results.
.then(function(finalResults) {
console.log("5");
for (var i = 0; i < finalResults.length; i++) {
console.log("6");
update(finalResults[i], function(result) {
console.log(result);
});
}
});
Full runnable code (get uses a timeout to simulate asynchronous calls)
function myFunction(array, array2) {
return Promise.resolve(true)
.then(function() {
console.log("1");
const promiseArray = array.map(function(item) {
return new Promise(function(resolve) {
get(item, function(results) {
console.log("2");
resolve(results);
});
});
});
return Promise.all(promiseArray);
})
.then(function(resultsArray) {
console.log("3");
const promiseArray2 = [];
for (var i = 0; i < array2.length; i += 2) {
const promise = new Promise(function(resolve) {
get(array2[i], function(results) {
console.log("4");
resolve(results);
});
});
promiseArray2.push(promise);
}
return Promise.all(promiseArray2).then(function(resultsArray2) {
return resultsArray.concat(resultsArray2);
});
})
.then(function(finalResults) {
console.log("5");
for (var i = 0; i < finalResults.length; i++) {
console.log("6");
update(finalResults[i]);
}
});
}
function get(item, cb) {
// Simply call the callback with the item after 1 second
setTimeout(() => cb(item), 1000);
}
function update(item) {
// Log what item is being updated
console.log(`Updated ${item}`);
}
// Test data
const array = ["arr1item1", "arr1item2", "arr1item3"];
const array2 = ["arr2item1", "arr2item2", "arr2item3"];
myFunction(array, array2);
Improving the code
The code now works as expected, but there are many improvements that make it a lot easier to understand and conveniently also shorter.
To simplify the code you can change your get function to return a promise. This makes it a lot easier, since you don't need to create a promise in every step. And update doesn't need to be a promise, neither does it need a callback as it's synchronous.
function get(array) {
return new Promise(function(resolve, reject) {
db.get(`SELECT .. FROM .. WHERE ..;`, function(error, row) {
if (err) {
return reject(error);
}
resolve(something);
});
});
}
Now you can use get everywhere you used to create a new promise. Note: I added the reject case when there is an error, and you'll have to take care of them with a .catch on the promise.
There are still too many unnecessary .then calls. First of all Promise.resolve(true) is useless since you can just return the promise of the first .then call directly. All it did in your example was to automatically wrap the result of it in a promise.
You're also using two .then calls to create an array of the results. Not only that, but they perform exactly the same call, namely get. Currently you also wait until the first set has finished until you execute the second set, but they can be all executed at the same time. Instead you can create an array of all the get promises and then wait for all of them to finish.
function myFunction(array, array2) {
// array.map(get) is equivalent to array.map(item => get(item))
// which in turn is equivalent to:
// array.map(function(item) {
// return get(item);
// })
const promiseArray = array.map(get);
for (let i = 0; i < array2.length; i += 2) {
promiseArray.push(get(array2[i]));
}
return Promise.all(promiseArray).then(results => results.forEach(update));
}
The myFunction body has been reduced from 32 lines of code (not counting the console.log("1") etc.) to 5.
Runnable Snippet
function myFunction(array, array2) {
const promiseArray = array.map(get);
for (let i = 0; i < array2.length; i += 2) {
promiseArray.push(get(array2[i]));
}
return Promise.all(promiseArray).then(results => results.forEach(update));
}
function get(item) {
console.log(`Starting get of ${item}`);
return new Promise((resolve, reject) => {
// Simply call the callback with the item after 1 second
setTimeout(() => resolve(item), 1000);
});
}
function update(item) {
// Log what item is being updated
console.log(`Updated ${item}`);
}
// Test data
const testArr1 = ["arr1item1", "arr1item2", "arr1item3"];
const testArr2 = ["arr2item1", "arr2item2", "arr2item3"];
myFunction(testArr1, testArr2).then(() => console.log("Updated all items"));

Resources