Unable to map inside an async function - node.js

In the below code, I am fetching data from an external api. After parsing the data as json, I wanted to map through it and get a modified version.
For some reason, the console.log(jsonData) inside the map function is not getting executed. Please check the code below for clarity
const getRandomOutfit = async (req, res) => {
const { gender, countryCode } = req.params;
if (req.params.gender === "FEMALE" || req.params.gender === "MALE") {
try {
const response = await fetch(URL);
const jsonData = await response.json();
const outputData = jsonData.map((productItem) => {
console.log(productItem); // doesn't get printed
// some operation
return productItem;
});
await res.json(jsonData);
} catch (error) {
res.status(500).send("Error getting data");
}
} else {
res.status(500).send("Invalid category");
}
};
I'm confused about what I am missing here and making an error.

I rewrote the code to make it clearer to understand. In general, it is best to take the fail first approach. Notice, how the first thing I do is return upon failure.
As to why you code is not printing anything out, try printing jsonData. It might be that this is an empty array.
const getRandomOutfit = async (req, res) => {
const { gender, countryCode } = req.params;
if (gender !== "FEMALE" && gender !== "MALE")
return res.status(500).send("Invalid category");
try {
const response = await fetch(URL);
const jsonData = await response.json();
console.log(jsonData); // what does this return?
const outputData = jsonData.map((productItem) => {
console.log(productItem); // doesn't get printed
// some operation
return productItem;
});
await res.json(jsonData);
} catch (error) {
res.status(500).send("Error getting data");
};

Related

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.

Return my function returns in my reponse using express (NodeJS, TypeScript)

I would like to know how I could send in my reply the return of my functions.
In this example I want to return in my response the error of my validator if I have one
Here is my SqlCompanyRepository.ts:
async create(company: CompanyCommand): Promise<CompanyEntity> {
const createCompany = await dataSource.getRepository(CompanyEntity).save(company)
await validate(company).then(errors => {
if (errors.length > 0) {
return errors;
}
});
return createCompany
}
And here is my index.ts:
app.post("/company", async (req, res) => {
const company = new CompanyCommand()
company.id = crypto.randomUUID()
company.name = req.body.name
company.email = req.body.email
company.password = req.body.password
const checkEmail = await companyRepository.findByEmail(company.email)
if (!!checkEmail) {
res.status(409).send("Email already exists.")
} else {
const companyResult = await companyRepository.create(company)
res.send(companyResult)
}
})
Replace return errors; with throw errors;, this causes the Promise<CompanyEntity> to be rejected with errors.
Then wrap the invocation in a try-catch-block to catch this rejection:
try {
const companyResult = await companyRepository.create(company)
res.send(companyResult)
} catch(errors) {
res.send(errors)
}

Problem to use a Map in Firebase Functions

I am trying to get the length of a Map and I keep getting "undefined". Could please someone tell me what am I doing wrong?
This is the part of the code that gives me problems.
const GYMdetail: { [key: string]: number} = {};
GYMdetail[`${doc.data().name} (${doc.data().personalID})`] = 650;
const subtotal = 650 * GYMdetail.size;
This is the complete function code
export const addGymMonthlyExpense =
functions.https.onRequest((request, response) => {
const query1 = admin.firestore().collection("users");
const query = query1.where("subscriptions.gym.active", "==", true);
query.get()
.then(async (allUsers) => {
allUsers.docs.forEach(async (doc) => {
if (doc != undefined) {
const houseForGym = doc.data().subscriptions.gym.house;
await admin.firestore()
.doc(`houses/${houseForGym}/expenses/2022-04`)
.get().then((snapshot) => {
if (snapshot.data() == undefined) {
console.log(`${houseForGym}-${doc.data().name}: CREAR!!`);
} else if (snapshot.data()!.issued == false) {
let detail: { [key: string]: any} = {};
const GYMdetail: { [key: string]: number} = {};
detail = snapshot.data()!.detail;
GYMdetail[
`${doc.data().name} (${doc.data().personalID})`
] = 650;
const subtotal = 650 * GYMdetail.size;
detail["GYM"] = {"total": subtotal, "detail": GYMdetail};
snapshot.ref.set({"detail": detail}, {merge: true});
}
return null;
})
.catch((error) => {
console.log(
`${houseForGym} - ${doc.data().name}: ${error}`);
response.status(500).send(error);
return null;
});
}
});
response.send("i");
})
.catch((error) => {
console.log(error);
response.status(500).send(error);
});
});
Since you are executing an asynchronous call to the database in your code, you need to return a promise from the top-level code; otherwise Cloud Functions may kill the container when the final } executes and by that time the database load won't be done yet.
So:
export const addGymMonthlyExpense =
functions.https.onRequest((request, response) => {
const query1 = admin.firestore().collection("users");
const query = query1.where("subscriptions.gym.active", "==", true);
return query.get()
...
Next you'll need to ensure that all the nested get() calls also get a chance to finish before the Functions container gets terminated. For that I recommend not using await for each nested get call, but a single Promise.all for all of them:
query.get()
.then(async (allUsers) => {
const promises = [];
allUsers.docs.forEach((doc) => {
const houseForGym = doc.data().subscriptions.gym.house;
promises.push(admin.firestore()
.doc(`houses/${houseForGym}/expenses/2022-04`)
.get().then((snapshot) => {
...
});
});
response.send("i");
return Promise.all(promises);
})
.catch((error) => {
console.log(error);
response.status(500).send(error);
});

Cannot get the result of an async function in controller

This is my controller:
const rssService = require('../services/rss.service');
async function parser() {
const result = await rssService.rssParser('someurl');
return result;
};
const parse = async function (req, res) {
const p = new Promise((resolve, reject) => {
const t = parser();
if (t === undefined) {
resolve(t);
} else {
// eslint-disable-next-line prefer-promise-reject-errors
reject('something bad happened');
}
});
p.then((result) => res.send(result)).catch((message) => console.log(`ERROR ${message}`));
};
module.exports = {
parse,
};
in the function : parser() above, I am trying to call my rss.service.js file which I have the logic. This file is a rss parser which tries to parse the feed and do some calculations (which needs promises and async) and then return the json object.
Here is how my rss.service look :
const rssParser = async function parseRssFeed(url) {
const parser = new Parser();
const appRoot = process.env.PWD;
const downloadDir = `${appRoot}/downloads/`;
if (!fs.existsSync(downloadDir)) {
fs.mkdirSync(downloadDir);
}
try {
const feed = await parser.parseURL('someurl');
const processedFeedItems = await Promise.all(feed.items.map(async (currentItem) => {
const {
fileUrl,
downloadPath,
} = await downloadFile(currentItem);
const hashValue = calculateHash(downloadPath)
return {
title: currentItem.title,
hash: hashValue,
url: mp3FileUrl,
};
}));
return (JSON.stringify(processedFeedItems));
} catch (error) {
console.error(error);
return 'error';
}
};
when I debug my code I can verify that Json object has been created with correct data, but the result does not return to the callee(controller).
I'll go in a little deeper since you mentioned you're new:
const rssService = require('../services/rss.service');
// This is an async function (always returns a promise)
async function parser() {
const result = await rssService.rssParser('someurl');
return result;
};
const parse = async function (req, res, next) {
// In await/async, you should use try/catch/throw instead of .then and .catch
// It does the same thing, but it's the preferred syntax and is "easier" to read IMO
// Out in "the community" people will complain if you mix await/async with promises like that
try {
// Adding await to ensure left-assign works.
// This is necessary because parser is an async function (so returns a promise)
const result = await parser();
// here, since you used `await` you get the value instead of the promise
if (result === undefined) throw new Error('something bad happened')
return res.send(result)
} catch (error) {
console.log(`ERROR ${error.message}`;
// Do you need to do anything else with this error? I assume something like:
return next(error);
}
};
module.exports = {
parse,
};
In a fast look, It seems you have forgot to wait for resolve the parser promise.
...
const p = new Promise(async(resolve, reject) => {
const t = await parser();
...

Promise.all returning undefined in Node JS

I have a code to fetch directory names from first API. For every directory, need to get the file name from a second API. I am using something like this in my Node JS code -
async function main_function(req, res) {
const response = await fetch(...)
.then((response) => {
if (response.ok) {
return response.text();
} else {
return "";
}
})
.then((data) => {
dirs = ...some logic to extract number of directories...
const tempPromises = [];
for (i = 0; i < dirs.length; i++) {
tempPromises.push(getFilename(i));
}
console.log(tempPromises); // Prints [ Promise { <pending> } ]
Promise.all(tempPromises).then((result_new) => {
console.log(result_new); // This prints "undefined"
res.send({ status: "ok" });
});
});
}
async function getFilename(inp_a) {
const response = await fetch(...)
.then((response) => {
if (response.ok) {
return response.text();
} else {
return "";
}
})
.then((data) => {
return new Promise((resolve) => {
resolve("Temp Name");
});
});
}
What am I missing here?
Your getFilename() doesn't seem to be returning anything i.e it's returning undefined. Try returning response at the end of the function,
async function getFilename(inp_a) {
const response = ...
return response;
}
Thanks to Mat J for the comment. I was able to simplify my code and also learn when no to use chaining.
Also thanks to Shadab's answer which helped me know that async function always returns a promise and it was that default promise being returned and not the actual string. Wasn't aware of that. (I am pretty new to JS)
Here's my final code/logic which works -
async function main_function(req,res){
try{
const response = await fetch(...)
const resp = await response.text();
dirs = ...some logic to extract number of directories...
const tempPromises = [];
for (i = 0; i < dirs.length; i++) {
tempPromises.push(getFilename(i));
}
Promise.all(tempPromises).then((result_new) => {
console.log(result_new);
res.send({ status: "ok" });
});
}
catch(err){
console.log(err)
res.send({"status" : "error"})
}
}
async function getFilename(inp_a) {
const response = await fetch(...)
respText = await response.text();
return("Temp Name"); //
}

Resources