function delaying on node.js - node.js

Im on a big problem, I need to do an application that gets trello content as soon as i can but i dont know why my for isnt working as it should do. when i the output of this should be the card id and when the function is called it should show the member. I dont know why, but when the 'miembro' function is called, it delays and it is shown after the second id, so its delayed a lot and i need them to show one under the other. I appreciate a quick answer, thank you!
const trelloKB = require("trello-kb");
const fetch = require('node-fetch');
// Replace this by the application key of your Trello account
var appKey = '51501902fff527d305686a29d6d61cfa';
// Replace this by a valid authorization token
var authToken = '9828f5f03073ae52ffdae77bdf49c939df8a315b169cb81aeb42a3d43d0f9e21';
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
function miembros (id){
fetch('https://api.trello.com/1/cards/'+id+'/members?key=51501902fff527d305686a29d6d61cfa&token=9828f5f03073ae52ffdae77bdf49c939df8a315b169cb81aeb42a3d43d0f9e21&fields=fullName', {
method: 'GET'
})
.then(response => {
setTimeout(function() {
return(response.text());
}, 3000);
})
.then(text => console.log(text))
.catch(err => console.error(err));
}
trelloKB.get(appKey, authToken, '33CP31Sf').then(
function (cards) {
// Print the title of each card
var ms = 3000;
for(i=0; i<2; i++){
var card = cards[0];
var id = card.id;
var titleCard = card.title;
console.log(id);
miembros(id);
}
},
);

I think you should learn about synchronous and asynchronous concept
You need to use async-await and return a promise in your function miembros()
read this async await and promise.
this is my example
const cards =[{id:1,title:'kimiwo'},{id:2,title:'namae wa'},{id:3,title:'udiiiin'}];
yourname(cards);
async function yourname (cards) {
for(let card of cards){
console.log(`id :${card.id},text:${card.title}`);
let result = await(await miembros(card.id)).text();
console.log(result);
}
}
function miembros(id){
return fetch('https://api.trello.com/1/cards/'+id+'/members?key=51501902fff527d305686a29d6d61cfa&token=9828f5f03073ae52ffdae77bdf49c939df8a315b169cb81aeb42a3d43d0f9e21&fields=fullName')
}
you can see the result here
*Edit
fetch returns promise, so you can just return fetch and wrap your function with async-await

It’s delayed because you need to use a sync await or .then
You need to get the first Id first, then do a .then to get the second ID through the function call.
Also, you shouldn’t show you API keys, they’re supposed to be private lol

Related

Node.js backend return response before all the API calls within the endpoints are made

I have a GET endpoint, which basically makes some API calls to the Spoonacular API. Essentially, I make two API calls within the endpoint.
The first API call gets the list of recipe ID's for the specific ingredients
The second API calls gets the metadata for each of the recipe ID's.
After the first API call I store all the Id's in an array (recipeArray), and I want to make the second api call for each ID in my array (function recipeTest does this).
When I try to do this and then return my response to the front end, it always returns a response before completing all the API calls in the second step.
Here, is my code. The first API calls works just fine, but the second API call (recipeTest function), is where it messed up. Before that function finishes making all the API calls to the Spoonacular API, my endpoint returns an empty Array (res.send(toSend)). So, I was just wondering if there is any way around this?
Thank you so much in advance, I really appreciate it!
module.exports = (app) => {
app.get('/api/search', async (req, res) => {
console.log("endpoint working");
let ingredientList = "apples,+eggs,+bacon"; // needs to be given from the front end
let ingredientSearchUrl = `https://api.spoonacular.com/recipes/findByIngredients?ingredients=${ingredientList}&number=1&ignorePantry=true&apiKey=${keys.spoonacularKey}`;
try {
const ingredientSearchResult = await axios({
method: 'get',
url: ingredientSearchUrl
});
var recipeArray = ingredientSearchResult.data.map(info => {
return info.id;
});
} catch (err) {
console.log("error in finding recipe ID ", err);
}
let toSend = [];
try {
const check = await recipeTest(recipeArray, toSend);
} catch (err) {
console.log("error in finding recipe information ", err);
}
res.send(toSend);
});
}
const recipeTest = async (recipeArray, toSend) => {
return Promise.all(
_.forEach(recipeArray, async (recipeId) => {
let recipeInfoUrl = `https://api.spoonacular.com/recipes/${recipeId}/information?includeNutrition=false&apiKey=${keys.spoonacularKey}`;
let recipeInfo = {};
const recipeData = await axios({
method: 'get',
url: recipeInfoUrl
});
// console.log("recipeInfo search working", recipeData.data);
recipeInfo['id'] = recipeData.data.id;
recipeInfo['title'] = recipeData.data.title;
recipeInfo['time'] = recipeData.data.readyInMinutes;
recipeInfo['recipeUrl'] = recipeData.data.sourceUrl;
recipeInfo['imageUrl'] = recipeData.data.image;
// console.log('recipe info dict', recipeInfo);
toSend.push(recipeInfo);
console.log('toSend inside', toSend);
})
);
}
_.forEach return collection itself and not all your async handlers.
Use recipeArray.map to get an array of async functions to let Promise.all do its work:
Promise.all(
recipeArray.map(x => async (recipeId) => {

Can't add key from function to dictionary

My code:
var price = {};
function getPrice(price) {
const https = require('https');
var item = ('M4A1-S | Decimator (Field-Tested)')
var body = '';
var price = {};
https.get('https://steamcommunity.com/market/priceoverview/?appid=730&market_hash_name=' + item, res => {
res.on('data', data => {
body += data;
})
res.on('end', () => price ['value'] = parseFloat(JSON.parse(body).median_price.substr(1))); //doesnt add to dict
}).on('error', error => console.error(error.message));
}
price['test'] = "123" //adds to dict fine
getPrice(price)
console.log(price);
Output:
{ test: '123' }
as you can see, the "test: 123" gets added, but the "value: xxx" from the function doesn't. Why is that?
There are two main problems here:
You're redeclaring the variable inside your function so you're declaring a separate, new variable and modifying that so the higher scoped variable, never gets your .value property.
You're assigning the property inside an asynchronous callback that runs sometime later after your function has returned and thus your function actually returns and you do the console.log() too soon before you have even obtained the value. This is a classic issue with return asynchronously obtained data from a function in Javascript. You will need to communicate back that data with a callback or with a promise.
I would also suggest that you use a higher level library that supports promises for getting your http request and parsing the results. There are many that already support promises, already read the whole response, already offer JSON parsing built-in, do appropriate error detection and propagation, etc... You don't need to write all that yourself. My favorite library for this is got(), but you can see a list of many good choices here. I would strongly advise that you use promises to communicate back your asynchronous result.
My suggestion for fixing this would be this code:
const got = require('got');
async function getPrice() {
const item = 'M4A1-S | Decimator (Field-Tested)';
const url = 'https://steamcommunity.com/market/priceoverview/?appid=730&market_hash_name=' + item;
const body = await got(url).json();
if (!body.success || !body.median_price) {
throw new Error('Could not obtain price');
}
return parseFloat(body.median_price.substr(1));
}
getPrice().then(value => {
// use value here
console.log(value);
}).catch(err => {
console.log(err);
});
When I run this, it logs 5.2.
You're actually console.logging .price before you're setting .value; .value isn't set until the asynchronous call fires.
You are declaring price again inside the function and also not waiting for the asynchronous task to finish.
const https = require("https");
const getPrice = () =>
new Promise((resolve, reject) => {
const item = "M4A1-S | Decimator (Field-Tested)";
let body = "";
return https
.get(
`https://steamcommunity.com/market/priceoverview/?appid=730&market_hash_name=${item}`,
res => {
res.on("data", data => {
body += data;
});
res.on("end", () =>
resolve(
parseFloat(JSON.parse(body).median_price.substr(1))
)
);
}
)
.on("error", error => reject(error));
});
const main = async () => {
try{
const price = await getPrice();
//use the price value to do something
}catch(error){
console.error(error);
}
};
main();

Synchronously iterate through firestore collection

I have a firebase callable function that does some batch processing on documents in a collection.
The steps are
Copy document to a separate collection, archive it
Run http request to third party service based on data in document
If 2 was successful, delete document
I'm having trouble with forcing the code to run synchronously. I can't figure out the correct await syntax.
async function archiveOrders (myCollection: string) {
//get documents in array for iterating
const currentOrders = [];
console.log('getting current orders');
await db.collection(myCollection).get().then(querySnapshot => {
querySnapshot.forEach(doc => {
currentOrders.push(doc.data());
});
});
console.log(currentOrders);
//copy Orders
currentOrders.forEach (async (doc) => {
if (something about doc data is true ) {
let id = "";
id = doc.id.toString();
await db.collection(myCollection).doc(id).set(doc);
console.log('this was copied: ' + id, doc);
}
});
}
To solve the problem I made a separate function call which returns a promise that I can await for.
I also leveraged the QuerySnapshot which returns an array of all the documents in this QuerySnapshot. See here for usage.
// from inside cloud function
// using firebase node.js admin sdk
const current_orders = await db.collection("currentOrders").get();
for (let index = 0; index < current_orders.docs.length; index++) {
const order = current_orders.docs[index];
await archive(order);
}
async function archive(doc) {
let docData = await doc.data();
if (conditional logic....) {
try {
// await make third party api request
await db.collection("currentOrders").doc(id).delete();
}
catch (err) {
console.log(err)
}
} //end if
} //end archive
Now i'm not familiar with firebase so you will have to tell me if there is something wrong with how i access the data.
You can use await Promise.all() to wait for all promises to resolve before you continue the execution of the function, Promise.all() will fire all requests simultaneously and will not wait for one to finish before firing the next one.
Also although the syntax of async/await looks synchronous, things still happen asynchronously
async function archiveOrders(myCollection: string) {
console.log('getting current orders')
const querySnapshot = await db.collection(myCollection).get()
const currentOrders = querySnapshot.docs.map(doc => doc.data())
console.log(currentOrders)
await Promise.all(currentOrders.map((doc) => {
if (something something) {
return db.collection(myCollection).doc(doc.id.toString()).set(doc)
}
}))
console.log('copied orders')
}

How to make multiple http requests from a Google Cloud Function (Cheerio, Node.js)

MY PROBLEM:
I'm building a web-scraper with Cheerio, Node.js, and Google Cloud Functions.
The problem is I need to make multiple requests, then write data from each request to a Firestore database before calling response.send() and thereby terminating the function.
My code requires two loops: the first loop is with urls from my db, with each one making a separate request. The second loop is with Cheerio using .each to scrape multiple rows of table data from the DOM and make a separate write for each row.
WHAT I'VE TRIED:
I've tried pushing each request to an array of promises and then waiting for all the promises to resolve with promises.all() before calling res.send(), but I'm still a little shaky on promises and not sure that is the right approach. (I have gotten the code to work for smaller datasets that way, but still inconsistently.)
I also tried creating each request as a new promise and using async/await to await each function call from the forEach loop to allow time for each request and write to fully finish so I could call res.send() afterward, but I found out that forEach doesn't support Async/await.
I tried to get around that with the p-iteration module but because its not actually forEach but rather a method on the query (doc.forEach()) I don't think it works like that.
So here's my code.
NOTE:
As mentioned, this is not everything I tried (I removed my promise attempts), but this should show what I am trying to accomplish.
export const getCurrentLogs = functions.https.onRequest((req, response) => {
//First, I make a query from my db to get the urls
// that I want the webscraper to loop through.
const ref = scheduleRef.get()
.then((snapshot) => {
snapshot.docs.forEach((doc) => {
const scheduleGame = doc.data()
const boxScoreUrl = scheduleGame.boxScoreURL
//Inside the forEach I call the request
// as a function with the url passed in
updatePlayerLogs("https://" + boxScoreUrl + "/");
});
})
.catch(err => {
console.log('Error getting schedule', err);
});
function updatePlayerLogs (url){
//Here I'm not sure on how to set these options
// to make sure the request stays open but I have tried
// lots of different things.
const options = {
uri: url,
Connection: 'keep-alive',
transform: function (body) {
return cheerio.load(body);
}
};
request(options)
.then(($) => {
//Below I loop through some table data
// on the dom with cheerio. Every loop
// in here needs to be written to firebase individually.
$('.stats-rows').find('tbody').children('tr').each(function(i, element){
const playerPage = $(element).children('td').eq(0).find('a').attr('href');
const pts = replaceDash($(element).children('td').eq(1).text());
const reb = replaceDash($(element).children('td').eq(2).text());
const ast = replaceDash($(element).children('td').eq(3).text());
const fg = replaceDash($(element).children('td').eq(4).text());
const _3pt = replaceDash($(element).children('td').eq(5).text());
const stl = replaceDash($(element).children('td').eq(9).text());
const blk = replaceDash($(element).children('td').eq(10).text());
const to = replaceDash($(element).children('td').eq(11).text());
const currentLog = {
'pts': + pts,
'reb': + reb,
'ast': + ast,
'fg': fgPer,
'3pt': + _3ptMade,
'stl': + stl,
'blk': + blk,
'to': + to
}
//here is the write
playersRef.doc(playerPage).update({
'currentLog': currentLog
})
.catch(error =>
console.error("Error adding document: ", error + " : " + url)
);
});
})
.catch((err) => {
console.log(err);
});
};
//Here I call response.send() to finish the function.
// I have tried doing this lots of different ways but
// whatever I try the response is being sent before all
// docs are written.
response.send("finished writing logs")
});
Everything I have tried either results in a deadline exceeded error (possibly because of quota limits which I have looked into but I don't think I should be exceeding) Or some unexplained error where the code doesn't finish executing but shows me nothing in the logs.
Please help, is there a way to use async/await in this scenario that I am not understanding? Is there a way to use promises to make this elegant?
Many thanks,
Maybe have a look at something like this. It uses Bluebird promises and the request-promise library
const Promise = require('bluebird');
var rp = require('request-promise');
const urlList = ['http://www.google.com', 'http://example.com']
async function getList() {
await Promise.map(urlList, (url, index, length) => {
return rp(url)
.then((response) => {
console.log(`${'\n\n\n'}${url}:${'\n'}${response}`);
return;
}).catch(async (err) => {
console.log(err);
return;
})
}, {
concurrency: 10
}); //end Promise.map
}
getList();

NodeJS Promise Firebase

Got to love nodeJS and asynchronous nature! With that, I'm dumbfounded how to to continue bc I can't keep nesting promises and of course that's a not go so I'm throwing up my hands bc each step requires a completed action with data from the previous step.
This is what I'm trying to accomplish and code is below.
A new college comes into /sessions/college
After getting the value of that key, go find advisors that subscribe to that college.
Get the FCM tokens for the subscribing advisors
Haven't even gotten to this part obviously, but send a FCM notification to subscribers.
Tada!
exports.newSessionNotifer = functions.database.ref('/sessions/college').onCreate((snap, context) => {
const college = snap.val();
var promises = [];
var getAdvisors = admin.database().ref('colleges').child(college).once('value').then((snapshot) => {
const people = snapshot.val();
var advisors = Object.keys(people);
return advisors;
}).then((advisors) => {
return advisors.forEach((token) => {
var advisorToken = admin.database().ref('users').child(token).child('fcmtoken').child('token').once('value');
return console.log(advisorToken);
});
});
return Promise.all(promises).then((values) => {
console.log(promises);
return console.log('Hi');
});
You're on the right track. once() returns a promise, and it is the set of promises from repeated calls to once that must be collected and run with Promise.all().
exports.newSessionNotifer = functions.database.ref('/sessions/college').onCreate((snap, context) => {
const college = snap.val();
return admin.database().ref('colleges').child(college).once('value');
}).then(snapshot => {
const people = snapshot.val();
let advisors = Object.keys(people);
let promises = advisors.map(token => {
return admin.database().ref('users').child(token).child('fcmtoken').child('token').once('value');
});
return Promise.all(promises);
});
EDIT Editing again, this time with the OP's answer in hand. On style, I'm not sure what lint says, but my definition of bad nesting style is when a then() block contains another then() block. Also regarding style, my approach to making this stuff comprehensible is to build (and test) small functions, one per async task.
On structure, the OP's new answer unnecessarily chains a second block after return advisors. Since advisors isn't a promise, we can carry on from there with synchronous code. Also on structure, the OP's solution creates a series of promises -- two for each advisor (get advisor token and push) -- but these are not certain to complete unless Promise.all is applied and returned.
Summing all that, my advice would be as follows...
On create, get the advisors for the college, send each a message.
exports.newSessionNotifer = functions.database.ref('/sessions/{sessionID}/college').onCreate((snap, context) => {
const college = snap.val();
return advisorsForCollege(college).then(advisors => {
let promises = advisors.map(advisor => sendAdvisorMessage(advisor, college));
return Promise.all(promises);
});
});
Advisors for a college are apparently the keys from that college object
function advisorsForCollege(college) {
return admin.database().ref('colleges').child(college).once('value').then(snapshot => Object.keys(snapshot.val()));
}
Sending an advisor message means getting the advisors token and doing a push. Return a two-promise chain that does that...
function sendAdvisorMessage(advisor, college) {
return tokenForAdvisor(advisor).then(token => {
let title = `There's a new session for ${college}!`;
let body = 'Go to the middle tab and swipe right to accept the session before your peers do!'
return sendToDevice(token, title, body);
});
}
Now we just need one to get an advisor's token and one to do a push...
function tokenForAdvisor(advisor) {
return admin.database().ref('users').child(advisor).child('fcmtoken').child('token').once('value');
}
function sendToDevice(token, title, body) {
const payload = { notification: { title: title, body: body } };
return admin.messaging().sendToDevice(token, payload);
};
I think lint should report all of the foregoing as just fine, even with promise nesting warning turned on.
Thanks to danh, here's my final code. Comment/feedback away! I decided to disable the promise nesting option within lint and viola!
exports.newSessionNotifer = functions.database.ref('/sessions/{sessionID}/college').onCreate((snap, context) => {
const college = snap.val();
return admin.database().ref('colleges').child(college).once('value').then((snapshot) => {
const people = snapshot.val();
let advisors = Object.keys(people);
return advisors;
}).then((advisors) => {
return advisors.map(advisor => {
return admin.database().ref('users').child(advisor).child('fcmtoken').child('token').once('value').then((snapshot) => {
const token = snapshot.val();
const payload = {
notification: {
title: `There's a new session for ${college}!`,
body: 'Go to the middle tab and swipe right to accept the session before your peers do!'
}
};
return admin.messaging().sendToDevice(token, payload);
});
});
});
});

Resources