async await question in Firebase Firestore - node.js

I tried to get the document and the subcollection data at once in firestore.
And I used the async and await to deal with the forEach loop.
It still has some problem. The console.log 4 always executes first.
But what I expect the should be 1 -> 2 -> 3 -> 4.
Could anyone help me how to redesign my code?
let data = {};
toolboxesRef.get()
.then(async snapshot => {
let toolboxes = [];
// get toolbox
await snapshot.forEach(async doc => {
let toolbox = {};
await toolboxesRef.doc(doc.id).collection('tags').get()
.then(snapshot => {
let tags = []
snapshot.forEach(doc => {
tags.push(doc.id);
console.log(1)
})
toolbox.tags = tags;
toolbox.id = doc.id;
toolbox.data = doc.data();
console.log(2)
})
console.log(3)
toolboxes.push(toolbox)
})
console.log(4);
data.toolboxes = toolboxes
return data;
})

export const asyncForEach = async (dataSnapshot, callback) => {
const toWait = [];
dataSnapshot.forEach(childSnapshot => {
toWait.push(childFunction((childSnapshot)));
});
await Promise.all(toWait);
};
Hey i updated the code because it seems that Firebase integrate it's own foreach function. Then in order to resolve i decided to call every function and store the promise that it return into an array then i use Promise.all to resolve an array of async function

You are using async operations inside forEach which doesn't work as you expect thm. You need to either use for..of or Promise.all. Try this version
const snapshot = await toolboxesRef.get();
const toolboxes = [];
for(const doc of snapshot) {
const toolbox = {};
const snapshot1 = await toolboxesRef.doc(doc.id).collection("tags").get();
const tags = [];
snapshot1.forEach(doc => {
tags.push(doc.id);
console.log(1);
});
toolbox.tags = tags;
toolbox.id = doc.id;
toolbox.data = doc.data();
console.log(2);
console.log(3);
toolboxes.push(toolbox);
}
console.log(4);
data.toolboxes = toolboxes;
return data;
You might need to tweak few things here and there but you will get an idea.

Related

NodeJS - Returning 'undefined'

I am learning NodeJS (generally I write in PHP). Please help me figure out the Promises. Now the 'getDataFromDir' function returns 'undefined'. I read the documentation, but apparently I don't fully understand something. Thanks in advance!
const host = 'localhost';
const port = 3000;
const __dirname = process.cwd();
async function getDataFromDir(fileName){
fsp
.readdir(path.join(fileName))
.then(async (indir) => {
const list = []
for (const item of indir) {
const src = await fsp.stat(path.join(fileName, item))
list.push(item)
}
return list;
})
}
const server = http.createServer(async (req, res) => {
const result = await getDataFromDir(__dirname);
result
.then((list) => {
console.log(list);
});
});
It seems like your return statement is returning only for the callback function under your .then statement. You should be able to use the await statement with your initial request and achieve a similar result.
async function getDataFromDir(fileName){
let indir = await fsp.readdir(path.join(fileName));
const list = [];
for (const item of indir) {
const src = await fsp.stat(path.join(fileName, item));
list.push(item);
}
return list;
}

Getting right fetch result from previous async fetch API functions

Hi I don't understand why getFlyByTime() function is giving me an ERR_INVALID_URL.
All the way upto getFlyByTime() I am getting the right results and coordinates.
Any advice would be appreciated,
Thank you
import fetch from "node-fetch";
let myIP = ''
let myLocation = ''
let flyByInformation = ''
const findIP = 'https://api.ipify.org/?format=json'
const geolocation = 'http://ipwho.is/'
const issFly = `https://iss-pass.herokuapp.com/json?`
const getMyIP = async function() {
let response = await fetch(findIP);
let data = await response.json()
return data.ip
}
const getMyGeoLocation = async function() {
let response = await fetch(geolocation + myIP);
let data = await response.json()
let resURL = `https://iss-pass.herokuapp.com/lat=${data.latitude}&lon=${data.longitude}`
return resURL;
}
const getFlyByTime = async function() {
let response = await fetch(myLocation);
console.log(response)
let data = await response.json()
return data;
}
getMyIP()
.then(data => {
myIP = data
}).catch(err => console.log('gmi error', err))
getMyGeoLocation()
.then(data => {
myLocation = data;
console.log(myLocation);
}).catch(err => console.log('gml error', err))
getFlyByTime()
.then(data => {
flyByInformation = JSON.parse(data);
console.log('flyby', flyByInformation);
}).catch(err => console.log('gflt error', err))
You are trying to use the myLocation and myIP values BEFORE they have been filled. Your functions return a promise before their work is done. Not until that promise has been fulfilled are the resolved values available for you to use.
As such, you must sequence your operations. It is generally easiest to do this with await. Here's an example shown below:
import fetch from "node-fetch";
const findIpURL = 'https://api.ipify.org/?format=json'
const geolocationURL = 'http://ipwho.is/'
const issFly = `https://iss-pass.herokuapp.com/json?`
async function fetchJSON(url) {
const response = await fetch(url);
if (response.ok) {
return response.json();
} else {
throw new Error(`Request failed, status ${response.status}`, { cause: response });
}
}
async function getMyIP() {
const data = await fetchJSON(findIpURL);
return data.ip;
}
function getMyGeoLocation(myIP) {
return fetchJSON(geolocationURL + myIP);
}
async function getFlyInfo(lat, long) {
let resURL = `https://iss-pass.herokuapp.com/lat=${lat}&lon=${long}`;
const flyInfo = await fetchJSON(resURL);
return flyInfo;
}
async function getFlyByTime() {
const myIP = await getMyIP();
console.log(myIP);
const myLocation = await getMyGeoLocation(myIP)
console.log(myLocation.latitude, myLocation.longitude);
return getFlyInfo(myLocation.latitude, myLocation.longitude);
}
getFlyByTime().then(flyInfo => {
console.log(flyInfo);
}).catch(err => {
console.log(err);
});
When I run this, the last getFlyInfo() request ends up returning a text/plain response that just says "Not Found" and the status is a 404. So, either the URL isn't being built properly in my version of the code or something is amiss in that last part.
But, hopefully you can see how you sequence asynchronous operations with await in this example and you can make that last part do what you want it to.

how to use promises in forEach loops node?

I have a page that displays user contacts. it retrieves an array of _ids from a specific user and the should iterate over these arrays to get corresponding contact information. However I get stuck with my async operations. In the current situation it resolves after the firs contactinformation was pushed to the invitationsFromFriendsData array. If I put the resolve outside the forEach loop, it resolves instantly without adding any contactinformation to the array. What am I doing wrong?
exports.contactBoard = async (req, res) => {
const username = req.params.username;
const doc = await User.findOne({'username': username}).exec();
const friendFiller = async () => {
return new Promise((resolve, reject)=> {
doc.invitationsFromFriends.forEach (async element => {
const doc1 = await User.findById(element).exec()
doc.invitationsFromFriendsData.push(doc1.username)
resolve('gelukt')
});
})};
const send = async () => {
return new Promise((resolve, reject)=> {
res.status(200).json({
message:"retrieved user contacts",
contacts: doc
})
console.log("Nu verzenden")
resolve ('gelukt')
})
};
const verzend = async() => {
let first = await friendFiller();
console.log('Ik wacht op het vullen')
let second = await send();
}
verzend();
}
You can use Promise.all to await an array of promises.
const friendFiller = async () => {
const friends = await Promise.all(doc.invitationsFromFriends.map((id) => User.findById(element).exec()));
friends.forEach(() => {
doc.invitationsFromFriendsData.push(doc1.username);
})
}
Having said that I think Mongoose can perform "joins" on your behalf with populate which I expect is more efficient as it'll issue a single DB query for all the friends.

Return value after all the promises are resolved

I am working on a nodejs code that fetches data from a site, parses it, finds particular data and fetches something else for the data that was previously fetched. But the final return statement is returning without the value fetched from the second API call.
I tried to implement async await, but I am not sure where do I have to put them exactly.
const getMainData = async val => {
let result = [];
//get xml data from the API
const xmlData = await getSiteContent(`value`); //axios call
parseString(xmlData, (err, json) => { //convert xml to json
const { entry } = json.feed; // array of results.
result = entry.map(report => {
const secondInfo = getSomeMoreData(report.something); //axios call
const data = {
id: report.id,
date: report.date,
title: report.title
};
data.info = secondInfo;
return data;
});
});
return { result };
};
I was expecting the function to return the array result that has id, date, title and info. But I am getting info as null since it is going to another function that does one more API call.
Try wrapping parseString in a promise so you can await the result, then make the entry.map callback an async function so that you can use the await keyword to wait for the result of the axios fetch.
async function xml2json(xml) {
return new Promise((resolve, reject) => {
parseString(xml, function (err, json) {
if (err)
reject(err);
else
resolve(json);
});
});
}
const getMainData = async val => {
//get xml data from the API
const xmlData = await getSiteContent(`value`); //axios call
const json = await xml2json(xmlData);
const { entry } = json.feed; // array of results
const result = await Promise.all(
entry.map(async report => {
const secondInfo = await getSomeMoreData(report.something); // axios call
const data = {
id: report.id,
date: report.date,
title: report.title,
};
data.info = secondInfo;
return data;
})
)
return { result };
}
Let me know if that works. If not, I can try to help you out further.
The problem with your code is you have mixed promises concept(async/await is a syntactic sugar - so same thing) along with callback concept.
And the return statement is outside callback() of parseString() and the callback would be executed maybe after returning results only because parseString() is an asynchronous function.
So in the following solution I have wrapped parseString() in a promise so that it can be awaited.
const parseStringPromisified = async xmlData => {
return new Promise((resolve, reject) => {
parseString(xmlData, (err, json) => {
if (err) {
reject(err);
}
resolve(json);
});
});
};
const getMainData = async val => {
//get xml data from the API
const xmlData = await getSiteContent(`value`); //axios call
const json = await parseStringPromisified(xmlData);
const { entry } = json.feed; // array of results.
const result = entry.map(async report => {
const secondInfo = await getSomeMoreData(report.something); //axios call
return {
id: report.id,
date: report.date,
title: report.title,
info: secondInfo
};
});
return Promises.all(result);
};

How to get the function returns with async/await methods

I am using cheerio to scrape job postings from indeed(at least 50 job postings).
I have already got the scraped data by using cheerio. But I don't know how to store these data in an object with async/await method
tools: node.js, cheerio, request.
const cheerio = require('cheerio');
const request = require('request');
const fetchIndeedData = async () => {
let start = 0;
let title = [];
let company = [];
let location = [];
let summary = [];
for (let i = 0; i < 5; i++) {
let url = `https://ca.indeed.com/jobs?q=full+stack+developer&l=Toronto,+ON&start=${start}`;
await request(url, (err, response, body) => {
const $ = cheerio.load(body);
$('#resultsCol .jobsearch-SerpJobCard .title a').each((i, item) => {
title.push(item.attribs.title);
});
$('#resultsCol .jobsearch-SerpJobCard .company a').each((i, item) => {
company.push($(item).text().trim());
});
$('#resultsCol .jobsearch-SerpJobCard .location').each((i, item) => {
location.push($(item).text());
});
$('#resultsCol .jobsearch-SerpJobCard .summary ').each((i, item) => {
summary.push($(item).text());
});
});
start += 10;
}
const jobPostings = {
title,
location,
company,
summary
};
return jobPostings;
};
const getData = fetchIndeedData().then(data => console.log(data));
I can't get any data when I call getData function.
And when I ran console.log(jobPostings) before return. I still can't get data....
Anyone has idea?
Here's what I think it might look like with async / await (you will need request-promise or similar):
const fetchIndeedData = async () => {
let urls = [0,1,2,3,4].map(start => `https://ca.indeed.com/jobs?q=full+stack+developer&l=Toronto,+ON&start=${start}`)
let responses = await Promise.all(urls.map(url => request.get(url)))
let $$ = responses.map(response => cheerio.load(response))
return $$.map($ => {
return {
title: $('title').text(),
// more cheerio code here
}
})
}
;(async function(){
const data = await fetchIndeedData()
console.log(data)
})()

Resources