JS Asynchronous undefined - javascript

I have read the following stack overflow post and this one, but i still can't find the solution to my problem.
I have function defined in a utility.js file as follows:
let geocodeOptions = {
provider: 'google',
httpAdapter: 'https',
apiKey: 'GMAPKEY_HERE',
formatter: null
};
async function postInformation(message, bot) {
try {
let messageContent = message.content;
let EncId = await getDetails(messageContent), msg
const NodeGeocoder = require('node-geocoder');
geocoder = NodeGeocoder(geocodeOptions);
geocoder.reverse({ lat: EncId[0], lon: EncId[1] }).then(res => {
msg = "*" + res[0]['city'] + "*";
})
let post;
let delayValue = await Bluebird.delay(2000)
if (delayValue) {
post = getMsg1() + '\n' + getMsg2() + '\n' + EncId;
return await post
}
} catch (error) {
console.log(error)
}
}
And in the main file, I am calling it as follows:
const util = require('./utility')
util.postInformation(message, client).then(value => {
console.log("value from main file")
console.log(value) // Always prints undefined
})
I also tried following:
let value = await util.postInformation(message, client);
console.log("value from main file")
console.log(value) // Always prints undefined
Instead of redirecting to other posts, pls provide a solution to this problem, where is the mistake i am making here.

I'm just going to leave this here for now
const NodeGeocoder = require('node-geocoder');
let geocodeOptions = {
provider: 'google',
httpAdapter: 'https',
apiKey: 'GMAPKEY_HERE',
formatter: null
};
async function postInformation(message, bot) {
try {
let messageContent = message.content;
let [lat, lon] = await getDetails(messageContent);
geocoder = NodeGeocoder(geocodeOptions);
let msg = await geocoder.reverse({lat, lon})
.then(([res]) => "*" + res.city + "*")
return getMsg1() + '\n' + getMsg2() + '\n' + EncId;
} catch (error) {
console.log(error)
}
}
OK - let me explain some (relevant) changes to your code
As NodeGeocoder returns a Promise, we can await, however, you need to return something in the .then, otherwise you get undefined
awaiting geocoder.reverse means no need for the (hacky) pause for 2 seconds, so, that's gone. Besides
let x = await Bluebird.delay(2000)
will always result in x being undefined - you'd want
let x = await Bluebird.delay(2000, true)
at least, if you want x to be truthy - but since it's just a delay, there's really no reason for testing the result of Bluebird.delay - because we already know it
The not so relevant changes:
I made a few little (ES2015+) changes, like shorthand arrow notation, and ([res]) => res.city instead of res => res[0]['city'] just to make the code neater
Also
let [lat, lon] = await getDetails(messageContent);
instead of
let EncId = await getDetails(messageContent)
and accessing lat as EndId[0] and lon as EndId[1] - also means
{ lat: EncId[0], lon: EncId[1] }
is just
{ lat, lon }

Related

Promise returns undefined nodejs

i am back with a same issue for my promise returning undefined please help.
Here, i am using ipfs to save data with savedata() which takes in a json string,a document name and a secret phrase.
i am not quit sure why promise is returning undefined i have checked everything
here is the new code
index.js
exports.savedata = async function savedata(jsondata,Docname,secretuuid){
const docname = Docname
let ipfss = await main();
let datavar = await ipfss.add(jsondata);
//check if input file exists or not?
const fpath = __dirname + "\\"+ "input.json"
if (fs.existsSync(fpath)) {
}
else{
const defobj = {defaultID:"defaultID"}
fs.writeFile("input.json",JSON.stringify(defobj),function(err){
// console.log('saved!')
})
}
//create an object and put an array with defaultid:defaultid to it
//take that object and keep concatenating the new arrays[new documents]
fs.readFile("input.json","utf-8",function(err,data){
if(err){
console.log(err)
}
const rembrk1 = data.replaceAll("{","")
const rembrk2 = rembrk1.replaceAll("}","")
const newstring = JSON.stringify({[docname]: datavar.path})
const URK = uuidv4() + "rkbyavds"
const CAT = CryptoJS.AES.encrypt(String(datavar.path),secretuuid);
var ENCAT = CAT.toString()
const fstring = "{" + rembrk2 + "," + docname + ":" + CAT + "}"
fs.writeFile("input.json", JSON.stringify(fstring),function(err){
if(err){
console.log(err)
}
})
return new Promise((resolve,reject) => {
// console.log('saved')
const retobj = {CAT:ENCAT,URK:URK}
resolve(retobj)
});
})
}
test.js
obj = JSON.stringify({user:"MqwMedz2edemusaaa",age:1291})
const op = savedata(obj,"doc1","verysecretuuid")
op.then(x=>{
console.log(x)
})
Well, the RegisterUser function executes the actions and retrieves the data. What it doesn't do is return any value. This means it'll return a Promise by default, but this Promise won't have any value to resolve.
Depending on which object you want to have returned, you need to return it or create and return a new Promise from which you can call the resolve-function.
You can read more about it here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function

.push() does not work under throw, using async await method to fetch data in a loop in javascript

I'm trying to fetch data one by one in order using async await and push the response.json to a array. However, the code I use below does not display the result in console.log(b);. Any one know what's the issue?
Thank you in advance!
async function fetch_data(){
let b = [];
for (let i = 1; i < 10; i++) {
let response = await fetch('https://SOME_URL/' + i, {
method: "GET",
headers: {"Key": "123456"}})
if (!response.ok) {
var error_detail = `An error has occured: ${response.status}`;
throw new Error(error_detail);
}
var data = await response.json();
// await b.push(data);
b.push(data);
}
// await Promise.all(b).then(function (b) {
// console.log(b)})
console.log(b);
return b;
}
When I run the script, it does not return anything in the console
fetch_data().catch(error => {
error.error_detail;
});
UPDATE: seems solved using my answer below. not sure why though.
The issue in which console.log(b); not displaying the output is because for example 'https://SOME_URL/' + i as i increases, IF when i = 5 and it returns error, then the console.log(b); will not return anything. If I set the i < 5, then console.log(b); will return the output b. So which means if any fetch in the loop returns error within the limit of i, then the push will not work (b will be empty) and hence console.log(b); will not return anything.
Anyone have idea how to solve this?
You need to rename your function. The error you get is Maximum call stack size exceeded since when you call fetch inside the loop you actually call the outer function, and not the function you want.
I updated several places
One of the issues would be the function name: "fetch" as same as the default fetch function name.
const fetchData = async(symbol) => {
try {
let promiseGroup = [];
const subFetch = async(i) => {
const BASE_URL = `https://reqres.in/api/products`
return fetch(`${BASE_URL}/${i}`, {
method: "GET",
headers: {
"Key": "123456"
}
}).then(res => res.json())
}
for (let i = 1; i < 10; i++) {
promiseGroup.push(i);
}
const results = await Promise.all(promiseGroup.map(subFetch))
console.log(results)
return result;
} catch (error) {
const error_detail = `An error has occured: ${error}`;
throw new Error(error_detail);
}
}
console.log(fetchData())
Here is a working version (at least its working for me):
Basically I just removed the header and indented it a bit, so your example should work as well. Maybe you have some issue on the backend ?
async function fetch_data() {
let b = [];
for (let i = 1; i < 10; i++) {
let response = await fetch('https://jsonplaceholder.typicode.com/todos/' + i, {
method: "GET",
});
if (!response.ok) {
var error_detail = `An error has occured: ${response.status}`;
throw new Error(error_detail);
}
var data = await response.json();
// await b.push(data);
b.push(data);
}
console.log(b);
return b;
}
fetch_data()
.then(data => console.log(data.length))
.catch(err => console.error(err));
Changing throw new Error(error_detail); to break seems solved the issue and able to display the result.

forEach loop behaving strangely with called function values logged at end of the loop instead of during

EDIT: currently i think the problem with this is that forEach is not promise aware. https://zellwk.com/blog/async-await-in-loops/
I am trying to apply a node javascript translation function (ive put it at the end of the post because it is quite long) to loop over an array of values. However when i loop for some reason i'm only getting certain parts of my looped function to appear after the loop has completed: Allow me to make this more clear:
array = [["hello", "hello" ],
["my", "my", ],
["name", "name" ],
["is", "my" ],
["joe", "joe"]]
function process (item,index){
const translate = require('#vitalets/google-translate-api');
console.log('loopaction'); //this shows that the loop is executing
translate(item[0], {to: 'sp'}).then(result => {
console.log(result.text);
}).catch(err => {
console.error(err);
})
array.forEach(process); // Applying the function process to the array in a ForEach loop
from this i am getting
loopaction
loopaction
loopaction
loopaction
loopaction
hola
mi
nombre
es
joe
So it seems that the forEach loop is completing before the values are being allowed to be displayed. Which is something i really don't understand since the array values are being translated correctly and then logged out in the correct order. As if they had been stored in the memory for later. And then called at the end of the forEach loop order.
The translate function looks like this:
function translate(text, opts, gotopts) {
opts = opts || {};
gotopts = gotopts || {};
var e;
[opts.from, opts.to].forEach(function (lang) {
if (lang && !languages.isSupported(lang)) {
e = new Error();
e.code = 400;
e.message = 'The language \'' + lang + '\' is not supported';
}
});
if (e) {
return new Promise(function (resolve, reject) {
reject(e);
});
}
opts.from = opts.from || 'auto';
opts.to = opts.to || 'en';
opts.tld = opts.tld || 'com';
opts.from = languages.getCode(opts.from);
opts.to = languages.getCode(opts.to);
var url = 'https://translate.google.' + opts.tld;
return got(url, gotopts).then(function (res) {
var data = {
'rpcids': 'MkEWBc',
'f.sid': extract('FdrFJe', res),
'bl': extract('cfb2h', res),
'hl': 'en-US',
'soc-app': 1,
'soc-platform': 1,
'soc-device': 1,
'_reqid': Math.floor(1000 + (Math.random() * 9000)),
'rt': 'c'
};
return data;
}).then(function (data) {
url = url + '/_/TranslateWebserverUi/data/batchexecute?' + querystring.stringify(data);
gotopts.body = 'f.req=' + encodeURIComponent(JSON.stringify([[['MkEWBc', JSON.stringify([[text, opts.from, opts.to, true], [null]]), null, 'generic']]])) + '&';
gotopts.headers['content-type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
return got.post(url, gotopts).then(function (res) {
var json = res.body.slice(6);
var length = '';
var result = {
text: '',
pronunciation: '',
from: {
language: {
didYouMean: false,
iso: ''
},
text: {
autoCorrected: false,
value: '',
didYouMean: false
}
},
raw: ''
};
try {
length = /^\d+/.exec(json)[0];
json = JSON.parse(json.slice(length.length, parseInt(length, 10) + length.length));
json = JSON.parse(json[0][2]);
result.raw = json;
} catch (e) {
return result;
}
if (json[1][0][0][5] === undefined) {
// translation not found, could be a hyperlink?
result.text = json[1][0][0][0];
} else {
json[1][0][0][5].forEach(function (obj) {
if (obj[0]) {
result.text += obj[0];
}
});
}
result.pronunciation = json[1][0][0][1];
// From language
if (json[0] && json[0][1] && json[0][1][1]) {
result.from.language.didYouMean = true;
result.from.language.iso = json[0][1][1][0];
} else if (json[1][3] === 'auto') {
result.from.language.iso = json[2];
} else {
result.from.language.iso = json[1][3];
}
// Did you mean & autocorrect
if (json[0] && json[0][1] && json[0][1][0]) {
var str = json[0][1][0][0][1];
str = str.replace(/<b>(<i>)?/g, '[');
str = str.replace(/(<\/i>)?<\/b>/g, ']');
result.from.text.value = str;
if (json[0][1][0][2] === 1) {
result.from.text.autoCorrected = true;
} else {
result.from.text.didYouMean = true;
}
}
return result;
}).catch(function (err) {
err.message += `\nUrl: ${url}`;
if (err.statusCode !== undefined && err.statusCode !== 200) {
err.code = 'BAD_REQUEST';
} else {
err.code = 'BAD_NETWORK';
}
throw err;
});
});
}
I realise there is a promise format and the problem im having may have to do with the asychronisity of the function and how the long the promise is taking to get resolved. I cant seem to figure out why the promise is not resolving or displaying after my forEach function is completely looped yet it seems to be saved correctly and in the correct order. Very odd.
Any ideas about what is it about the function translate() that is making this happen? Is there anyway i can rewrite my function process () to make sure that the translate functions resolved promise and the .then() in function process () is fully executed before moving on?
You are correct, you are using promises, so translate() will run asynchronously (in the background) while the rest of your code is executing. That is why you go through all the foreach() before the translate function returns, and therefore you get that output.
However, there is also a problem using a forEach loop in an async function or a promise block. The callback function is not being awaited. Therefore, the promise chain is broken, resulting in the unexpected behavior.
Don't use forEach loop in a promise or async function. Instead, use a for loop to iterate through the items of the array:
To avoid these problems, change the forEach loop to a For loop and use async and await like this:
async function process (item,index){
const translate = require('#vitalets/google-translate-api');
console.log('loopaction'); //this shows that the loop is executing
await translate(item[0], {to: 'sp'})
.then(result => {
console.log(result.text);
})
.catch(err => {
console.error(err);
})
}
async function main() {
array = [["hello", "hello" ],
["my", "my" ],
["name", "name" ],
["is", "my" ],
["joe", "joe"]]
for (let i = 0; i < array.length; i++) {
await process(array[i], i);
}
}
main()
await makes the function wait until the promise is resolved.
NOTE: You tried to create a timeout with object.sleep(), this doesn't exist in javascript, use setTimeout() instead, refer to: Sleep() in Javascript

Promise condition equal

I want to wait in my code until two values are the same. For this I use
await new Promise((resolve, reject) => {
if(curCount == maxTests) resolve;
});
But I think, this is only called one time. How I can make it that if both values are the same the promise is resolved? How to avoid that it will never send a resolve?
UPDATE:
Some requested the function that makes trouble. Here is the whole function, without it sub function. The function will fill the q-queue to fullfill the tests sync. The problem ist that req.body.selection.forEach immediately returns but I want to wait until the whole queue is ready. So my idea was to add a promise to the end and hit until current and max are the same.
router.post('/imgtest', async (req, res) => {
console.log('Start Image Test');
//Er kommt an.
req.setTimeout(5000000); // Nach Adam Riese 83 Minuten.
process.setMaxListeners(0);
io = req.app.get('socketio');
//Caluclate the max amounnt of tests
const maxTests = req.body.selection.length * req.body.servers.length;
var curCount = 0;
//RETURN IF THIS IS READY. CURRENTLY IT RETURNS IMMEDIATLY
req.body.selection.forEach(async function(entry) {
//Jetzt erstmal die Domain aus der DB holen
var dbUrl = await getUrl(entry);
console.log('tapsi');
var bildFormat = '';
var arrQuestionmark = dbUrl.split('?');
if(arrQuestionmark.length==2){
if(arrQuestionmark[1].includes('&')){
var arrAnd = arrQuestionmark[1].split('&');
arrAnd.forEach(function(entry) {
if(entry.includes('format=')){
var arrFormat = entry.split('=');
bildFormat = arrFormat[1];
}
});
}
}
var masterName = uuidv1();
const orgpath = path.resolve(__basedir, 'tests/downloads', masterName + '-.' + bildFormat);
//Download the MAsterimage
(async () => {
await queue.add(() =>downloadImage(dbUrl, 'c11', req.body.domain, bildFormat, orgpath) );
})();
req.body.servers.forEach(async function(xserver) {
var fileName = masterName + '-' + xserver + '.' + bildFormat;
const dpath = path.resolve(__basedir, 'tests/downloads', fileName);
(async () => {
await queue.add(() => downloadImage(dbUrl, xserver, req.body.domain, bildFormat, dpath));
//console.log('Done ' + entry);
})();
(async () => {
await queue.add(async() => startCompare(orgpath, dpath, 'null:').then(function(result) {
console.log(result);
curCount++;
messageIO(curCount,maxTests);
}));
//console.log('done compare ' + entry);
//fs.unlinkSync(dpath);
})();
});
});
console.log('Need to wait');
res.sendStatus(200);
});
You're correct in assuming that will only be called once. A way around that is to, within the function, create a loop via setInterval - doing a regular check and resolving if true and clearing the loop.
Not too sure what trying to achieve but one thing is for certain, Array.prototype.forEach() will not await even if its callback is async and performing a test inside a new Promise(...) constructor won't help.
Good news though, for loops will await.
Here's the code with for loops instead of .forEach() (twice), unnecessary stuff removed, and otherwise tidied up.
First a suggestion ...
// https://nodejs.org/api/querystring.html
// The querystring module provides utilities for parsing and formatting URL query strings.
const querystring = require('querystring');
... then:
router.post('/imgtest', async (req, res) => {
req.setTimeout(5000000); // Nach Adam Riese 83 Minuten.
process.setMaxListeners(0);
// io = req.app.get('socketio'); // not used
const masterName = uuidv1(); // moved from inner loop
for (i=0; i<req.body.selection.length; i++) {
let entry = req.body.selection[i];
let dbUrl = await getUrl(entry);
let bildFormat = querystring.parse(dbUrl).format;
let orgpath = path.resolve(__basedir, 'tests/downloads', `${masterName}-.${bildFormat}`);
await queue.add(() => downloadImage(dbUrl, 'c11', req.body.domain, bildFormat, orgpath));
for (j=0; j<req.body.servers.length; j++) {
let xserver = req.body.servers[j];
let dpath = path.resolve(__basedir, 'tests/downloads', `${masterName}-${xserver}.${bildFormat}`);
await queue.add(() => downloadImage(dbUrl, xserver, req.body.domain, bildFormat, dpath));
await queue.add(async() => startCompare(orgpath, dpath, 'null:'); // probably safe to remove `async`
// fs.unlinkSync(dpath); // ???
}
}
res.sendStatus(200);
});
This should get you started. I expect that you still have some way to go. At the very least you need to add a try/catch structure and be prepared to send error/status back to the client.

Async/ await in Firebase Cloud Function says Error Expression has type `void`

I'm trying to use async/await in my firebase function but I am getting an error. I have marked the function as async but when I try to use await inside of it I get error: Expression has type void. Put it on its own line as a statement.
I think this is strange because I think it each await function call is already in its own line as a statement. So, I'm not sure what to do. Any help would be appreciated.
For clarity, I am making a request with the Cheerio web scrape library, and then trying to make two async function calls during each loop with the .each method.
export const helloWorld = functions.https.onRequest((req, response) => {
const options = {
uri: 'https://www.cbssports.com/nba/scoreboard/',
transform: function (body) {
return cheerio.load(body);
}
};
request(options)
.then(($) => {
$('.live-update').each((i, element) => {
const homeTeamAbbr = $(element).find('tbody').children('tr').eq(0).find('a').html().split("alt/").pop().split('.svg')[0];
const awayTeamAbbr = $(element).find('tbody').children('tr').eq(1).find('a').html().split("alt/").pop().split('.svg')[0];
const homeTeam = $(element).find('tbody').children('tr').eq(0).find('a.team').text().trim();
const awayTeam = $(element).find('tbody').children('tr').eq(1).find('a.team').text().trim();
let homeTeamStatsURL = $(element).find('tbody').children('tr').eq(0).find('td').html();
let awayTeamStatsURL = $(element).find('tbody').children('tr').eq(1).find('td').html();
const gameTime = $(element).find('.pregame-date').text().trim();
homeTeamStatsURL = homeTeamStatsURL.match(/href="([^"]*)/)[1] + "roster";
awayTeamStatsURL = awayTeamStatsURL.match(/href="([^"]*)/)[1] + "roster";
const matchupString = awayTeamAbbr + "#" + homeTeamAbbr;
const URLString = "NBA_" + urlDate + "_" + matchupString;
// var docRef = database.collection('NBASchedule').doc("UpcommingSchedule");
// var boxScoreURL = "www.cbssports.com/nba/gametracker/boxscore/" + URLString;
// var setAda = docRef.set({[URLString]:{
// homeTeam: homeTeam,
// awayTeam: awayTeam,
// date: gameTime,
// homeTeamAbbr: homeTeamAbbr,
// awayTeamAbbr: awayTeamAbbr,
// homeTeamStatsURL: homeTeamStatsURL,
// awayTeamStatsURL: awayTeamStatsURL,
// boxScoreURL: boxScoreURL
// }}, { merge: true });
getTeamPlayers(homeTeamStatsURL, matchupString);
getTeamPlayers(awayTeamStatsURL, matchupString);
console.log("retrieved schedule for "+ matchupString + " on " + urlDate)
});
response.send("retrieved schedule");
})
.catch(function (err) {
console.log("error " + err);
});
});
The function I am calling just makes another request and then I'm trying to log some data.
function getTeamPlayers(playerStatsURL, matchupString) {
const options = {
uri: playerStatsURL,
transform: function (body) {
return cheerio.load(body);
}
};
console.log(playerStatsURL + " stats url");
request(options)
.then(($) => {
console.log('inside cheerio')
$('tbody').children('tr').each(function(i, element){
const playerName = $(element).children('td').eq(1).children('span').eq(1).find('a').text().trim();
const injury = $(element).children('td').eq(1).children('span').eq(1).children('.icon-moon-injury').text().trim();
const news = $(element).children('td').eq(1).children('span').eq(1).children('.icon-moon-news').text().trim();
const playerUrl = $(element).children('td').eq(1).children('span').eq(1).find('a').attr('href');
const playerLogsUrl = "https://www.cbssports.com" + playerUrl.replace('playerpage', 'player/gamelogs/2018');
console.log(playerName + ": Inj: " + injury + " News: " + news);
// database.collection('NBAPlayers').add({[playerName]:{
// '01 playerName': playerName,
// '03 playerLogsUrl': playerLogsUrl,
// '04 inj': injury,
// '05 news': news
// }})
// .then(docRef => {
// console.log("ID " + docRef.id);
// //getPlayerLogs(playerLogsUrl, playerName, docRef.id);
// })
// .catch(error => console.error("Error adding document: ", error));
});
});
}
async/await is supported in the Node version 8 (which is deployable as Cloud Functions for Firebase). Specifically, use 8.6.1 (at the time of this writing).
As for awaiting x2 inside a loop - I don't think this is best practice.
Instead, push all these requests into an array, then Promise.all so as to fetch all in parallel.
just in case someone comes after. As Ron stated before, you shouldn't use async/await inside a traditional for loop if the second call doesn't need the value of the first, as you will increase the run time of the code.
Furthermore you can't use async/await inside of a forEach=> or a map=> loop, as it won't stop and wait for the promise to resolve.
The most efficient way is to use Promise.all([]). Here I leave a great youtube video of a crack that explains async/await and Promises => https://www.youtube.com/watch?v=vn3tm0quoqE&t=6s
As to one of the questions in the comments by DarkHorse:
But all I want is for getTeamPlayers() to execute and write to the database so I don't know why I need to return anything.
In Firebase Functions all functions need to return something before the final response.
For example in this case, he has created an http function. Before he ends the function with response.send("retrieved schedule"); you need to finish each and every function triggered. As Firebase Functions will clean up after the final response, erasing and stoping anything that it is still running. So any function that hasn't finish will be killed before doing its job.
Returning a promise is the way Firebase Functions knows when all executions have finished and can clean up.
Hop it helps :)

Categories

Resources