result[0] is recognized as undefined - javascript

So this function uses fetch to get a list of team names and pass them on to the getTeams() function
export function process_all_teams(func) {
fetch('/teams')
.then((response) => {
return response.json();
})
.then((result) => {
return getTeams(result);
})
.then((results) => {
js_avg_goals(results);
})
.catch((error) => console.log(error))
}
The getTeams function then uses the names to retrieve the list of players from the /teams/<team_name> url to create and return an array of js Team objects.
function getTeams(result){
let promises = Array(12);
for(let i=0; i < result.length; i++){
let url = '/teams/' + result[i];
promises[i] = fetch(url).then((response) => {return response.json()});
}
let teams = Array(12);
Promise.all(promises)
.then((res) => {
for(let i=0; i < res.length; i++){
teams[i] = new Team(res[i]);
}
})
.catch(() => console.log("There was an error in getTeams"))
return teams;
}
In the js_avg_goals function I'm trying to log out results[0] but it returns 'undefined' while results returns the actual array of Team objects.
export function js_avg_goals(results) {
console.log(results);
console.log(results[0]);
}
This is the Team constructor. The first item of the array res is the team name while the rest are players that are represented as player objects converted to json.
function Team(res){
this.name = res[0];
this.players = res.slice(-15);
}
I don't understand why this is happening somebody please help me.

Promise.all returns a new promise.
Modify getTeams to return that promise, something like this:
return Promise.all(promises)
.then(res => res.map(r => new Team(r)))
.catch(() => console.log("There was an error in getTeams"))

Related

How to have a function finish a for loop before returning

Below is the code I type to go through an array and stick the data needed into an output array, then ultimately return the output array:
var stocks = ['AAPL', 'BMY']
let getInfo = arr => {
let output = []
arr.forEach(i => {
var root = 'https://fmpcloud.io/api/v3/profile/' + i + '?apikey=myAPIKey'
axios.get(root)
.then((data) => {
output.push(data.data)
})
.catch((e) => {
console.log('error', e)
})
})
console.log('output: ', output)
return output
}
getInfo(stocks)
The console.log just logs an empty array, which makes me think it goes to the return statement before the for loop finishes executing. Does anybody know the best way to have the for loop finish first, and then finally return the output array?
You can use async/await keywords to get the axios call to wait. Like:
let getInfo = async arr => {
let output = []
try {
for(var i in arr) {
var root = 'https://fmpcloud.io/api/v3/profile/' + arr[i] + '?apikey=myAPIKey'
var data = await axios.get(root);
output.push(data.data);
}
}
catch(e) {
// oops
console.log('error', e);
return null;
}
return output;
}
That's because axios is the asynchronous method and return statement should be returned before the axios().then().
Use Promise.all() and use async/await
var stocks = ['AAPL', 'BMY']
let getInfo = async arr => {
let output = []
const promises = []
arr.forEach(i => {
var root = 'https://fmpcloud.io/api/v3/profile/' + i + '?apikey=myAPIKey'
promises.push(axios.get(root))
.then((data) => {
output.push(data.data)
})
.catch((e) => {
console.log('error', e)
})
})
const result = await Promise.all(promises)
output = result.map(r => r.data)
console.log('output: ', output)
return output
}
getInfo(stocks)
Axios.get returns a promise, it is executed asynchronously while the rest of your lambda continues its execution.
Look into Promise.all() so that you can log output after all the promises have resolved.
var stocks = ["AAPL", "BMY"];
let getInfo = async (arr) => {
const output = await Promise.all(
arr.map(
(i) =>
new Promise(async (resolve, reject) => {
try {
const { data } = await axios.get(
`https://fmpcloud.io/api/v3/profile/${i}?apikey=myAPIKey`
);
resolve(data);
} catch (err) {
reject(err);
}
})
)
);
console.log("output: ", output);
return output;
};
getInfo(stocks);
Don't use forEach. Use a for/of loop. Next, use async/await to synchronize on the
axios calls. That should do it.

How to await an array that is filled by data fetched from promises to get filled?

I currently have some promises that must fill an array, but the problem is that I cant operate with that array because it gets read before the promises finish, and im unable to log it in any way. I have read about promise.all but I dont understand how to implement it.
Any idea?
//array to be filled
var members = []
//function to check if an user is a member
function checkIfMember(uid, cid){
return ctx.getChatMember(uid, cid).then(data =>{
if(data.status == "member"){
return true;
}else{
return false;
}
}).catch(err =>{
console.log(err)
})
}
//Returns all the user and the number of users
db.query("SELECT * FROM users").then(data => {
console.log(data)
console.log(data.length)
//in the returned user list we check that each member is in the group, if it is, we push the member to an array
for(let i in data){
checkIfMember(data[i].tg_id,chatid).then(res =>{
if(res){
members.push(data[i].tg_id)
}
}).catch(err =>{
console.log(err)
})
}
console.log(members) //EMPTY --> how or where do i log this? !!QUESTION <<<<<------------
}).catch(err => {
console.log(err)
})
You'll need to handle a promises list, and each promise (which is the checkIfMemeber) will be pushed to that array.
Then, you can use Promise.all which will trigget then when all the promises in the list are resolved:
//Returns all the user and the number of users
db.query("SELECT * FROM users").then(data => {
console.log(data)
console.log(data.length)
//in the returned user list we check that each member is in the group, if it is, we push the member to an array
let promiseList = []
for(let i in data){
let promise = checkIfMember(data[i].tg_id,chatid).then(res =>{
if(res){
members.push(data[i].tg_id)
}
}).catch(err =>{
console.log(err)
})
promiseList .push(promise)
}
Promise.all(promiseList).then((res) => {
console.log(members);
});
}).catch(err => {
console.log(err)
})
This is one of the possible solutions:
//array to be filled
var members = []
//function to check if an user is a member
function checkIfMember(uid, cid) {
return ctx.getChatMember(uid, cid).then(data => {
return data.status === "member";
}).catch(err => {
console.log(err)
})
}
//Returns all the user and the number of users
db.query("SELECT * FROM users").then(data => {
console.log(data)
console.log(data.length)
const promises = data.map(user => {
return checkIfMember(user.tg_id, chatid).then(isMember => {
if (isMember) {
members.push(user.tg_id);
}
})
});
Promise.all(promises).then(() => {
console.log(members)
});
}).catch(err => {
console.log(err)
})
But this code is still not very good.

writeFile does not wait for variable to be instantiated

I'm new to node.js and javascript in general but I am having issues understanding why the writeFile function is writing a blank file. I think the for loop should be a Promise but I am not sure how to write it.
const removeProviders = function () {
readline.question('Where is the file located? ', function(filePath) {
let providerArray = fs.readFileSync(filePath).toString().split('\r\n');
console.log(providerArray);
let importFile = '';
for (let providerId in providerArray) {
getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
})
}
fs.writeFile('C:/path/to/file.txt', importFile);
You can collect all the promises from the for-loop into an Array and then pass them into Promise.all() which will resolve only after all of them resolved. If one of the promises are rejected, it will reject as well:
const promises = providerArray.map((item) => {
return getProvider(item)
.then((response) => {
let providerInfo = response.data;
return providerInfo;
})
.then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
});
});
Promise.all(promises).then(() => {
fs.writeFile('C:/path/to/file.txt', importFile, err => {
if (err) {
console.error(err);
}
});
});
While doing this you could also get rid of the importFile variable and collect directly the results of your promises. Promise.all().then(results => {}) will then give you an array of all results. Thus no need for an updatable variable.
The below approach may be useful.
I don't know your getProvider return Promise. you can set promise in getProvider method
const getProviderValue = async function(providerArray) {
return new Promise((resolve, reject) {
let importFile = '';
for (let providerId in providerArray) {
await getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += "entry";
})
}
resolve(importFile)
})
}
const removeProviders = async function () {
readline.question('Where is the file located? ', function(filePath) {
let providerArray = fs.readFileSync(filePath).toString().split('\r\n');
console.log(providerArray);
let importFile = await getProviderValue(providerArray)
fs.writeFile('C:/path/to/file.txt', importFile);
})
}
Your for loop does not wait for the promises to resolve. A better way to approach this problem would be to use reduce.
providerArray.reduce(
(p, _, i) => {
p.then(_ => new Promise(resolve =>
getProvider(providerArray[providerId]).then((response) => {
let providerInfo = response.data;
return providerInfo;
}).then((providerInfo) => {
let entry = createImportEntry(providerInfo);
importFile += entry;
resolve();
}))
);
}
, Promise.resolve() );
You can also use Promise.all but the out data may not be in the order that you expect if you append to the importFile variable.

Firestore query for loop with multiple values

I am attempting to retrieve a number of Firestore documents using data held in a string. The idea is that for each value in the array, i'd use Firestore query to retrieve the document matching that query and push it to another array. I am having a few issues achieving this. So far i've tried:
exports.findMultipleItems = functions.https.onRequest((request, response) => {
var list = ["item1", "item2", "item3", "item4"];
var outputList = [];
for (var i = 0; i < list.length; i++) {
console.log("Current item: " + list[i]);
let queryRef = db.collection("items").where('listedItems', 'array-contains', list[i]).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
}
snapshot.forEach(doc => {
outputList.push(doc.data());
});
return;
})
.catch(err => {
console.log('Error getting documents', err);
});
}
response.send(JSON.stringify(outputList));
});
I'm not entirely sure but i think one of the issues is that the for loop is being completed before the queries have a chance to finish.
P.s - this is being ran through Cloud Functions using Admin SDK.
Your queryRef is not actually a reference. It's a promise that resolves after your get/then/catch have finished. You need to use these promises to determine when they're all complete. The array will be populated only after they are all complete, and only then is it safe to send the response using that array.
Collect all the promises into an array, and use Promise.all() to get a new promise that resolves after they're all complete:
exports.findMultipleItems = functions.https.onRequest((request, response) => {
var list = ["item1", "item2", "item3", "item4"];
var outputList = [];
const promises = [];
for (var i = 0; i < list.length; i++) {
console.log("Current item: " + list[i]);
let promise = db.collection("items").where('listedItems', 'array-contains', list[i]).get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
}
snapshot.forEach(doc => {
outputList.push(doc.data());
});
return;
})
.catch(err => {
console.log('Error getting documents', err);
});
promises.push(promise);
}
Promise.all(promises).then(() => {
response.send(JSON.stringify(outputList));
}
.catch(err => {
response.status(500);
})
});
You might want to use these tutorials to better understand how to deal with promises in Cloud Functions:
https://firebase.google.com/docs/functions/video-series/
You need to look into promises, Never called a promise in a loop like this. First, you need to chunk your code which returns result from the DB(asynchronously) and uses Promise.all() to handle multiple promises.
utils.getData = async (item) => {
try {
const result = await db.collection("items").where('listedItems', 'array-contains', item).get();
return result;
} catch (err) {
throw err;
}
};
utils.getDataFromDB = async () => {
try {
const list = ["item1", "item2", "item3", "item4"];
const outputList = [];
const promises = [];
for (var i = 0; i < list.length; i++) {
console.log("Current item: " + list[i]);
const element = list[i];
promises.push(utils.getData(elem));
}
const result = await Promise.all(promises);
result.forEach((r) => {
if (r.empty) {
console.log('No matching documents.');
} else {
snapshot.forEach(doc => {
outputList.push(doc.data());
});
}
});
return outputList;
} catch (err) {
throw err;
}
}
module.exports = utils;
Here is my attempt at fully idiomatic solution. It needs no intermediate variables (no race conditions possible) and it separates concerns nicely.
function data_for_snapshot( snapshot ) {
if ( snapshot && !snapshot.empty )
return snapshot.map( doc => doc.data() );
return [];
}
function query_data( search ) {
return new Promise( (resolve, reject) => {
db
.collection("items")
.where('listedItems', 'array-contains', search)
.get()
.then( snapshot => resolve(snapshot) )
.catch( resolve( [] ) );
});
}
function get_data( items )
{
return new Promise( (resolve) => {
Promise
.all( items.map( item => query_data(item) ) )
.then( (snapshots) => {
resolve( snapshots.flatMap(
snapshot => data_for_snapshot(snapshot)
));
});
});
}
get_data( ["item1", "item2", "item3", "item4"] ).then( function(data) {
console.log( JSON.stringify(data) );
});
I used a simple mockup for my testing, as i don't have access to that particular database. But it should work.
function query_data( search ) {
return new Promise( (resolve, reject) => {
setTimeout( () => {
resolve([{
data: function() { return search.toUpperCase() },
empty: false
}])
});
});
}

Using Promise.all

Firstly apologies if this is a simple problem to solve but I am using node-fetch and the concept of Promises for the first time. I am calling an api endpoint which returns paginated data so i get multiple links for further data.
I am handling this by putting all the links into an array and then I want to call each endpoint in the array and just print out the data to the console for now
So my first call is
var urlArray = [];
fetch(`${BASE_URL}/leagues?api_token=${AUTH_TOKEN}`, { headers: headers })
.then(function(response){
return response.json(); // pass the data as promise to next then block
})
.then(function(json){
// Collect Paginated Results
var number_of_pages = parseInt(json.meta['pagination']['total_pages']);
for (i = 2; i < number_of_pages + 1; i++) {
urlArray.push(`${BASE_URL}?page=${i}&api_token=${AUTH_TOKEN}`)
}
})
.catch(function(error) {
console.log(error);
});
So once this done i have an array of urls i need to hit sequentially, could anyone give me some pointers please on how to then setup a promise to hit each one? or can i have something where is do it in my for loop?
Thanks
sorry if this is wrong!
At the beginning, put:
var promise_array = [];
in the for loop, replace with
for (i = 2; i < number_of_pages + 1; i++) {
var url = `${BASE_URL}?page=${i}&api_token=${AUTH_TOKEN}`;
urlArray.push(url);
promise_array.push(fetch(url));
}
At the end of the function put
Promise.all(promise_array).then(all_responses => {
// .. pluck json, then continue ...
});
You can achieve this by:
fetch(`${BASE_URL}/leagues?api_token=${AUTH_TOKEN}`, { headers: headers }).then(response => {
var json = response.json();
var numberOfPages = parseInt(json.meta['pagination']['total_pages']);
var urlArray = [];
for (i = 2; i < numberOfPages + 1; i++) {
urlArray.push(`${BASE_URL}?page=${i}&api_token=${AUTH_TOKEN}`)
}
var promises = urlArray.map(url => getSomethingByUrl(url));
return Promise.all(promises);
}).then(results => {
console.log(results);
});
You can use Array.prototype.reduce to create Promise chain so they will be executed one after another
urls.reduce((prev, item) => {
return prev.then(() => {
return someAction(item);
})
}, Promise.resolve()).then(() => {
console.log('All done');
}).catch((err) => {
console.log(err);
});
Edit:
As mentioned by Bergi, value from someAction should be returned in case it's asynchronous (return Promise). If someAction is asynchronous it must return Promise.
"use strict";
function someAction(i) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(i);
resolve(i);
}, i*1000);
});
}
const urls = [1,2,3,4,5];
urls.reduce((prev, item) => {
return prev.then(() => {
return someAction(item);
})
}, Promise.resolve()).then(() => {
console.log('All done');
}).catch((err) => {
console.log(err);
});

Categories