I'm trying to iterate and print out in order an array in Javascript that contains the title of 2 events that I obtained from doing web scraping to a website but it prints out in disorder. I know Javascript is asynchronous but I'm new in this world of asynchronism. How can I implement the loop for to print the array in order and give customized info?
agent.add('...') is like console.log('...'). I'm doing a chatbot with DialogFlow and NodeJs 8 but that's not important at this moment. I used console.log() in the return just for debug.
I tried the next:
async function printEvent(event){
agent.add(event)
}
async function runLoop(eventsTitles){
for (let i = 0; i<eventsTitles.length; i++){
aux = await printEvent(eventsTitles[i])
}
}
But i got this error error Unexpected await inside a loop no-await-in-loop
async function showEvents(agent) {
const cheerio = require('cheerio');
const rp = require('request-promise');
const options = {
uri: 'https://www.utb.edu.co/eventos',
transform: function (body) {
return cheerio.load(body);
}
}
return rp(options)
.then($ => {
//** HERE START THE PROBLEM**
var eventsTitles = [] // array of event's titles
agent.add(`This mont we have these events available: \n`)
$('.product-title').each(function (i, elem) {
var event = $(this).text()
eventsTitles.push(event)
})
agent.add(`${eventsTitles}`) // The array prints out in order but if i iterate it, it prints out in disorder.
// *** IMPLEMENT LOOP FOR ***
agent.add(`To obtain more info click on this link https://www.utb.edu.co/eventos`)
return console.log(`Show available events`);
}).catch(err => {
agent.add(`${err}`)
return console.log(err)
})
}
I would like to always print out Event's title #1 and after Event's title #2. Something like this:
events titles.forEach((index,event) => {
agent.add(`${index}. ${event}`) // remember this is like console.log(`${index}. ${event}`)
})
Thanks for any help and explanation!
There no async case here but if you still face difficultly than use this loop
for (let index = 0; index < eventsTitles.length; index++) {
const element = eventsTitles[index];
agent.add(${index}. ${element})
}
Related
Context
I'm retrieving data from the ESPN API to fetch weekly NFL matchup data. So, I'm making 18 api calls each time I need to fetch this data to account for all 18 weeks in the NFL season. I'm then creating an array with the data I need from the responses to those calls and writing out 18 files that align with each week in the NFL season (week1.json, week2.json, etc.).
Problem
The problem is that when I call my endpoint, I am seeing 2 things intermittently, and not necessarily at the same time:
(1) Some of the json files(week1.json, week2.json, etc.) include only a portion of the expected array. So, instead of 16 objects in the array, I may see only 4, or only 6, etc. Why would I only see a portion of the response data written to the array that's ultimately written to the .json files?
(2) Not all files are written to each time the endpoint is called. So, I may see that only week1-week5's .json files are written. Why aren't all of them updated?
Problem Code
// iterate 18 times
for (let i = 0; i < 18; i++) {
let weekNumber;
weekNumber = i + 1;
const week = fs.readFileSync(`./pickem/week${weekNumber}.json`, 'utf8');
const weekJson = JSON.parse(week);
// empty weekJson.games array
weekJson.games = []
// get all items
axios.get(`https://sports.core.api.espn.com/v2/sports/football/leagues/nfl/seasons/2022/types/2/weeks/${weekNumber}/events?lang=en®ion=us`)
.then(response => {
const schedule = [];
// get all items from response
const items = response.data.items
// console.log(response.data.items)
items.forEach(item => {
// make get call to $ref
axios.get(item.$ref)
.then(response => {
// get name
const name = response.data.name
// get date
const date = response.data.date
// get event id
const eventid = response.data.id
// get team ids
let team1 = response.data.competitions[0].competitors[0].id
let team2 = response.data.competitions[0].competitors[1].id
// create new object
const newObject = {
name: name,
date: date,
eventid: eventid,
team1: team1,
team2: team2
}
// add games for week
weekJson.games.push(newObject);
fs.writeFileSync(`./pickem/week${weekNumber}.json`, JSON.stringify(weekJson));
})
.catch(error => {
console.log(error)
})
})
}).catch(error => {
console.log(error)
})
}
Updated Code
router.get('/getschedules', (req, res) => {
async function writeGames() {
// iterate 18 times
for (let i = 0; i < 18; i++) {
let weekNumber;
weekNumber = i + 1;
const week = fs.readFileSync(`./pickem/week${weekNumber}.json`, 'utf8');
const weekJson = JSON.parse(week);
// empty weekJson.games array
weekJson.games = []
// get all items
// Add await keyword to wait for a week to be processed before going to the next one
await axios.get(`https://sports.core.api.espn.com/v2/sports/football/leagues/nfl/seasons/2022/types/2/weeks/${weekNumber}/events?lang=en®ion=us`)
.then(async (response) => { // add async to be able to use await
const schedule = [];
// get all items from response
const items = response.data.items
console.log(response.data.items)
// Use standard loop to be able to benefit from async/await
for (let item of items) {
// make get call to $ref
// wait for an item to be processed before going to the next one
await axios.get(item.$ref)
.then(response => {
// get name
const name = response.data.name
// get date
const date = response.data.date
// get event id
const eventid = response.data.id
// get team ids
let team1 = response.data.competitions[0].competitors[0].id
let team2 = response.data.competitions[0].competitors[1].id
// create new object
const newObject = {
name: name,
date: date,
eventid: eventid,
team1: team1,
team2: team2
}
// add games for week
weekJson.games.push(newObject);
})
.catch(error => {
console.log(error)
})
}
// moved out of the for loop since you only need to write this once
fs.writeFileSync(`./pickem/week${weekNumber}.json`, JSON.stringify(weekJson));
}).catch(error => {
console.log(error)
})
}
}
writeGames();
})
Your issue might come from the fact that you are looping over an array of item that triggers parallel asynchronous calls and write weekJson before you get the entire data. (But theoretically your code should work if writeSyncFile is really synchronous, maybe there are locks on the file system that prevents node to write properly?)
You could try to make everything sequential and only write weekJson once instead of everytime you go over an item:
EDIT
I updated my original code proposition by keeping parallel calls and it worked for me (it's similar to OP's code but I only write the json file once per week).
Then I tried to run OP's code and it was working fine as well. So this makes me think that the problem isn't from the code itself but rather how it's called. As a pure node script, there doesn't seem to be any issue. But I just noticed that OP is using it server side as the result of an API call.
Having an API write so many JSON concurrently is probably not the best idea (especially if the api is called multiple times almost simultaneously). You could either
just return the games in the response
or precompute the results
or fetch and write them only once then cache the result to be reused
Then I wonder if due to the server context, there is not some kind of timeout since OP said that with my initial solution, only the first week was created.
const axios = require("axios");
const fs = require("fs");
async function writeGames() {
const writeWeekGamesPromises = [];
// iterate 18 times
for (let weekNumber = 1; weekNumber < 19; weekNumber++) {
// give week a default value in case the json file doesn't exist (for repro purpose)
let week = "{}";
try {
week = fs.readFileSync(`./pickem/week${weekNumber}.json`, "utf8");
} catch (e) {
console.log(`error reading week ${weekNumber} json file:`, e);
// file doesn't exist yet
}
const weekJson = JSON.parse(week);
// empty weekJson.games array
const games = [];
weekJson.games = games;
// get all items
// Add await keyword to wait for a week to be processed before going to the next one
writeWeekGamesPromises.push(axios
.get(
`https://sports.core.api.espn.com/v2/sports/football/leagues/nfl/seasons/2022/types/2/weeks/${weekNumber}/events?lang=en®ion=us`
)
.then(async (eventListResponse) => {
// add async to be able to use await
const schedule = [];
console.log(JSON.stringify(eventListResponse.data),'\n');
// get all items from response
const items = eventListResponse.data.items;
// console.log(eventListResponse.data.items); // this seems to be useless since we log the whole data just above
// parallelize calls and wait for all games from a week to be fetched before writing the file
await Promise.all(
items.map((item) => {
// we return the promise so that Promise.all will wait for all games to be pushed before going on writing the file
return axios
.get(item.$ref)
.then((response) => {
// get name, date and eventid
const {name, date, id: eventid} = response.data;
// get team ids
let team1 = response.data.competitions[0].competitors[0].id;
let team2 = response.data.competitions[0].competitors[1].id;
games.push({ name, date, eventid, team1, team2 });
})
.catch((error) => {
console.log(error);
});
})
);
// Now that all game data is ready, write in the file
fs.writeFileSync(
`./pickem/week${weekNumber}.json`,
JSON.stringify(weekJson)
);
})
.catch((error) => {
console.log(error);
}));
}
// Waiting for all games from all weeks to be processed
await Promise.all(writeWeekGamesPromises);
}
async function runAndLogTime() {
const start = Date.now();
await writeGames();
console.log(`took ${(Date.now() - start) / 1000}s to write all json files`);
}
runAndLogTime();
Good Afternoon,
I am using the MERN stack to making a simple invoice application.
I have a function that runs 2 forEach() that goes through the invoices in the DB and the Users. if the emails match then it gives the invoices for that user.
When I log DBElement to the console it works, it has the proper data, but when I log test1 to the console (app.get()) it only has one object not both.
// forEach() function
function matchUserAndInvoice(dbInvoices, dbUsers) {
dbInvoices.forEach((DBElement) => {
dbUsers.forEach((userElement) => {
if(DBElement.customer_email === userElement.email){
const arrayNew = [DBElement];
arrayNew.push(DBElement);
app.set('test', arrayNew);
}
})
})
}
// end point that triggers the function and uses the data.
app.get('/test', async (req,res) => {
const invoices = app.get('Invoices');
const users = await fetchUsersFromDB().catch((e) => {console.log(e)});
matchUserAndInvoice(invoices,users,res);
const test1 = await app.get('test');
console.log(test1);
res.json(test1);
})
function matchUserAndInvoice(dbInvoices, dbUsers) {
let newArray = [];
dbInvoices.forEach((DBElement) => {
dbUsers.forEach(async(userElement) => {
if(DBElement.customer_email === userElement.email){
newArray.push(DBElement);
app.set('test', newArray);
}
})
})
}
app.set('test', DBElement); overrides the existing DBElement, so only the last matching DBElement is shown in test1.
If you want to have test correspond to all matching DBElement, you should set it to an array, and then append a new DBElement to the array each time it matches inside the for-loop:
if(DBElement.customer_email === userElement.email){
let newArray = await app.get('test');
newArray.push(DBElement);
app.set('test', newArray);
}
This is my first question on stack overflow, so bear with me.
So I have a controller function that is rendering my google maps api, and I am trying to loop through the results from mongoDB and push them to an array so that I can pass it to a script tag on the ejs. I am able to console.log(myArr) within the for loop and get results, but not outside of it (just above the res.render). I am assuming my that my problem is my result within res.render is receiving an empty array.
Please help me I have been stuck on this problem for days now. Thank you, Andrew
function showMap(req, res) {
let myArr = [];
db.collection("poppies").find().toArray(function (err, result) {
for (let i = 0; i < result.length; i++) {
myArr.push(result[i].Address);
};
});
res.render('map', {
result: JSON.stringify(myArr)
})
};
Asynchronous Javascript allows you to execute operations without waiting for the processing thread to become free.
Imagine the data loads from your DB only after 3 seconds - you should "wait" to get the value from your DB before running the next line code.
In your case you use myArr "outside" without await -Or- promises -Or- callbacks) - So the value is an empty array.
IMPORTANT: The idea of Asynchronous Javascript is a topic for a course
(No way to cover this issue by StackOverflow Answer).
Not working
length is: undefined
function helloWorld() {
let items = db.collection("newspapers").find({}).toArray();
return (items);
};
const result = helloWorld();
console.log("length is: " + result.length); /* length is: undefined */
Working
await for the value from helloWorld() function.
function helloWorld() {
let items = db.collection("newspapers").find({}).toArray();
return (items);
};
const result = await helloWorld();
console.log("length is: " + result.length); /* length is: 337 */
In practice
Promise chaining for example:
For this database:
[
{
"Newspaper": "The New York Times"
},
{
"Newspaper": "Washington Post"
}
]
const dbName = 'my_database';
await client.connect();
const db = client.db(dbName);
myArr = [];
db.collection("newspapers").find({}).toArray()
.then(
res => {
for (let i = 0; i < res.length; i++) {
myArr.push(res[i].Newspaper);
};
console.log(`The length is ${res.length} documents`) /* The length is 2 documents */
console.log(myArr); /* [ 'The New York Times', 'Washington Post' ] */
},
err => console.error(`Something went wrong: ${err}`),
);
try/catch Async/await example (Readable code pattern):
const helloWorld = (async () => {
try {
let items = await db.collection("newspapers").find({}).limit(2).toArray();
return (items);
} catch (e) {
console.error(
`Unable to establish a collection: ${e}`,
)
}
})
const result = await helloWorld();
console.log("length is: " + result.length); /* length is: 2 */
More examples here:
https://docs.mongodb.com/drivers/node/fundamentals/promises
SCENARIO:
Using mssql I'm connecting to sql and retrieving a list of ids, then based on those id I want to run stored procedures. What I'm currently doing is running the first stored proc, storing the id's in an array, then I'm running a for loop calling another module, where I pass the id to run a stored proc. This works fine when I've got a single id, but fails with 'Global connection already exists. Call sql.close() first.' when I try to run multiple ones.
How do I create connect to sql, run my query, then run the next one? What's the best approach?
The code below runs the stored proc with ids and causes the above error.
exports.runStoredProc = function (query,id) {
sql.connect(config.config).then(()=>{
return sql.query`${query} ${id}`
}).then(res=> {
do something with the response
}).catch(error => {
console.log(error)
})
}
Looks like the connection still exists when the below bit of code runs it using next id. I thought that creating a Promise will force to await execution before it runs the above bit of code again?
let toRun = result.recordset.length
let gen = async num => {
for(let i=0;i<num;i++) {
var resp = result.recordset[i].id
console.log(i, resp)
var sp = report
var reportId = await new Promise(() => db.runStoredProc(sp,resp))
}
}
gen(toRun).then(() => console.log("done!"))
You need to return Promise from runStoredProc
exports.runStoredProc = function (query,id) {
return sql.connect(config.config).then(()=>{
return sql.query`${query} ${id}`
}).then(res=> {
do something with the response
}).catch(error => {
console.log(error)
})
}
and no need to wrap db.runStoredProc in loop
let toRun = result.recordset.length
let gen = async num => {
for(let i=0;i<num;i++) {
var resp = result.recordset[i].id
console.log(i, resp)
var sp = report
var reportId = await db.runStoredProc(sp,resp)
}
}
gen(toRun).then(() => console.log("done!"))
I have been working to make a small app to get the images and info from the pokemon api at http://pokeapi.co/ , I am following course using promises however I can get a group (example: water type) which give me 78 objects , each one with a resource_uri which is the data which give me the information of each pokemon.
Until now I have make this and I can get all the objects, however, now how can I make in order to get for each object push in the console.log (or later use them) all the 78pokemons with the data of each.
My code until now is this>
var $ = window.jQuery
var base = 'http://pokeapi.co/api/v1/egg/'
Promise.resolve($.get(base + '2'))
.then((results) =>
.then((results) => {
var pokechara = results.pokemon
var basechara = 'http://pokeapi.co'
var promises = []
for (let chara of pokechara){
var pokech = pokechara[i]
'${something}'
var pokechurl = basechara + pokechara[0].resource_uri
promises.push(Promise.resolve($.get(pokechurl)))
}
debugger
return Promise.all(promises)
})
.then((poke) => {
console.log(poke)
})
.catch((err) => {
debugger
})
I really wanted to make something with the api of yugioh.wikia but I dont see how I can get work because the problem with the Cros-server or header. To see a preview of what I am doing you can see at http://www.kengreg.com/yugiohapp/
Thanks in advance
Actually, in the code used in your reply to Alex, the string variables you use to call the api are the only thing wrong, the 'http://' part at the start is needed. Apart from that, your code works fine.
var $ = window.jQuery;
var base = 'http://pokeapi.co/api/v1/egg/';
Promise.resolve($.get(base + '2'))
.then((results) => {
var pokechara = results.pokemon;
var basechara = 'http://pokeapi.co/';
var promises = [];
for (var i in pokechara){
var pokech = pokechara[i];
var pokechurl = basechara + pokechara[0].resource_uri;
promises.push(Promise.resolve($.get(pokechurl)));
}
debugger
return Promise.all(promises);
})
.then((poke) => {
debugger;
console.log(poke);
})
.catch((err) => { debugger })
I left the debugger so you can checkout the results of the promises. The poke variable at the end should have the objects array you wanted.