Foreach and push() with async await doesn't push the values - node.js

Here's my code:
app.get("/bots/:id", async (req, res)=>{
var bot = await Bots.findOne({id: req.params.id});
if(!bot) return await res.send("not there");
bot.desc = await marked(bot.desc);
var user = req.user;
bot.owner = [];
await bot.owners.forEach(async (id)=>{
await fetch(`${process.env.DOMAIN}/api/client/users/${id}`).then(r=>r.json()).then(async d=>{
await bot.owner.push(d.tag);
});
});
await console.log(bot.owner);
await res.render('botpage.ejs', {user, bot});
})
App is express, fetch is node-fetch and I'm using ejs to render.
When I do console.log(bot.owner), it logs [] and not the array of d.tags that I fetch.
Everything is fine except this, including my api and ejs page.
process.env.DOMAIN = https://discord.rovelstars.com

foreach loop not wait. use for loop instead of foreach.
for(int i=0;i<bot.owners.length();i++){
....your task
}
or
for(var obj of bot.owners){
...your task
}
visit this for more details

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
}

Puppeteer - ExpressJS infinite loop a function

I have a problem with my Express JS app : When I'm trying to call a function, this function is endlessly called ... It opens a lot of chromium browser and cause performance issues ...
I just want to call this function one time.
I've found a solution to make it work (And called just one time), but in this situation I can't pass any parameters ...
const farm = (async () => {
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
await page.goto("https://www.example.com/?s=" + term);
await page.waitForSelector("div");
const postLinks = await page.evaluate(() => {
let postLinks = [];
let elements = document.querySelectorAll('div.article');
for (element of elements) {
postLinks.push({
title: element.querySelector('div.meta-info > h3 > a')?.textContent,
url: element.querySelector('div.meta-info > h3 > a')?.href
})
}
return postLinks;
});
console.log(postLinks);
await browser.close();
})();
app.get('/', (req, res) => {
var term = "Drake";
res.send(farm);
});
With the code below, I can pass parameters but I can't return the result in "res.send", and the function is called endlessly :
const farm = async (term) => {
const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();
await page.goto("https://www.example.com/?s=" + term);
await page.waitForSelector("div");
const postLinks = await page.evaluate(() => {
let postLinks = [];
let elements = document.querySelectorAll('div.article');
for (element of elements) {
postLinks.push({
title: element.querySelector('div.meta-info > h3 > a')?.textContent,
url: element.querySelector('div.meta-info > h3 > a')?.href
})
}
return postLinks;
});
console.log(postLinks);
await browser.close();
}
app.get('/', (req, res) => {
var term = "Drake";
var results = farm(term);
res.send(results);
});
Did I miss something ?
Thanks !
It's not an infinite loop, but unresolved promise. The farm returns a promise, which you're not waiting for, but instead send the pending promise before it resolves, i.e. before the puppeteer is done.
You need to wait for farm's promise to resolve, make middleware function async and add await to the farm call:
app.get('/', async(req, res) => {
var term = "Drake";
// farm returns a promise, so you need to wait for it to resolve, i.e. block execution
// otherwise it just sends pending promise, because node.js runs in non-blocking fashion
var results = await farm(term);
res.send(results);
});

loop through documents in a collection to get fields

I have a collection called users in firebase firestore. Each document in the collection users is a user registered in the app. Each document has a field called token_ids. How can I loop through all the documents to get the values in the token_ids field. I am using firebase cloud functions to do so. Here is the code snippet I tried using but it did not work:
const functions = require('firebase-functions');
const admin = require ('firebase-admin');
admin.initializeApp();
//fetch all token ids of users
const tokenReference = admin.firestore().collection("users");
const tokenSnapshot = await tokenReference.get();
tokenSnapshot.forEach((doc) =>{
console.log("Token ids are:" + doc.data().token_id);
});
});
Took me a while but finally found the solution to it. Here it is. It is the first solution given by Dhruv Shah but slightly modified :
async function fetchAllTTokenIds() {
const tokenReference = admin.firestore().collection("users");
const tokenSnapshot = await tokenReference.get();
const results = [];
tokenSnapshot.forEach(doc => {
results.push(doc.data().token_id);
});
const tokenIds = await Promise.all(results);
return console.log("Here =>" +tokenIds);
}
Since Firestore operations are asynchronous, you should ideally wrap your code in an async-await block.
For example:
async function fetchAllTTokenIds() {
const tokenReference = admin.firestore().collection("users");
const tokenSnapshot = await tokenReference.get();
const results = [];
// Recommendation: use for-of loops, if you intend to execute asynchronous operations in a loop.
for(const doc of tokenSnapShot) {
results.push(doc.data().token_id);
}
const tokenIds = await Promise.all(results);
}
In this way all the tokenIds variable will be populated with an array of tokenIds.
Alternatively, you can also make all the asynchronous calls in parallel since they are independent of each other using Promise.all (Reference)
async function fetchAllTTokenIds() {
const tokenReference = admin.firestore().collection("users");
const tokenSnapshot = await tokenReference.get();
const tokenIds = await Promise.all(tokenSnapShot.map(doc => {
return doc.data()
.then(data => (data.token_id))
}))
In this case, the tokenIds variable will contain the array of all the tokenIds.
How the code snippet will be structured depends whether you're using the Firebase Admin SDK, be it as a script ran on your local machine or a httpsCallable being called by a client app. For the first case, it is written like this:
In your script file.js, after initialising app, write the following code.
exports.test_f = async function() {
try {
const tokenReference = admin.firestore().collection("users");
const tokenSnapshot = await tokenReference.get();
tokenSnapshot.forEach((doc) =>{
console.log("Token ids are:" + doc.data().token_id);
});
} catch (error) {
console.log(error);
}
}
exports.test_f();
Run this script on your command line using the command node file.js, which will print the provided output

How to push an object into an array in async function

i have been trying to insert an object into an array in async function ,but it
return an empty array as output in nodejs ,mongoose
var data = [];
app.get("/api/post", async (req, res) => {
const post = await UserPost.find();
post.forEach(async element => {
const email = await element.userid;
const user = await Account.find({ email });
const usern = await user[0].username;
var userobject = {
element,
usern
};
//Promise.all(userobject)
data.push(userobject);
});
console.log(data);
res.send({ data });
});
It seems you are struggling with promises. In order to achieve this specific scenario, you can use Promise.all and Array.map.
Here is a code I edited for you:
(*please note that this is just a dummy code for the sake of explanation)
app.get("/api/post", async (req, res) => {
try {
const posts = await dummyPromiseResolver(); // first promise
const promises = posts.map(async element => {
const user = await dummyEmailReturn(element.userid); // second promise
const usern = user[0].username;
return {
usern,
...element
};
});
const fresult = await Promise.all(promises);
res.send(fresult);
} catch (error) {
console.error("error in posts fetch:" + error);
}
});
If I describe this code, posts.map is creating an Array of promises since we need to iterate through every object in the array and needs to add values from separate promises.
Then Promise.all can execute your promise array and return final results array with your desired results.
Note: You can also use for … of as well but when we need to happen things parallelly we use Promise.all. You can find more information from this thread.
here is a link for code sandbox: https://codesandbox.io/embed/serverless-cookies-nu4h0
Please note that I have added dummyPromiseResolver and dummyEmailReturn which would be equal to UserPost.find() and Account.find() functions respectively. In addition to that, I removed a few unnecessary awaits in your code. I added a try catch block to catch any exceptions. You can change that try catch as you please.
hope this will help you. let me know if you need more clarifications.

How to pass dynamic page automation commands to puppeteer from external file?

I'm trying to pass dynamic page automation commands to puppeteer from an external file. I'm new to puppeteer and node so I apologize in advance.
// app.js
// ========
app.get('/test', (req, res) =>
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://testurl.com');
var events = require('./events.json');
for(var i=0;i<events.length;i++){
var tmp = events[i];
await page.evaluate((tmp) => { return Promise.resolve(tmp.event); }, tmp);
}
await browser.close();
})());
My events json file looks like:
// events.json
// ========
[
{
"event":"page.waitFor(4000)"
},
{
"event":"page.click('#aLogin')"
},
{
"event":"page.waitFor(1000)"
}
]
I've tried several variations of the above as well as importing a module that passes the page object to one of the module function, but nothing has worked. Can anyone tell me if this is possible and, if so, how to better achieve this?
The solution is actually very simple and straightforward. You just have to understand how this works.
First of all, you cannot pass page elements like that to evaluate. Instead you can do the following,
On a seperate file,
module.exports = async function getCommands(page) {
return Promise.all([
await page.waitFor(4000),
await page.click("#aLogin"),
await page.waitFor(1000)
]);
};
Now on your main file,
await require('./events.js').getCommands(page);
There, it's done! It'll execute all commands for you one by one just as you wanted.
Here is a complete code with some adjustments,
const puppeteer = require("puppeteer");
async function getCommands(page) {
return Promise.all([
await page.title(),
await page.waitFor(1000)
]);
};
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto("https://example.com");
let data = await getCommands(page);
console.log(data);
await page.close();
await browser.close();
})();

Resources