Getting 'undefined' from asynchronous response despite 'await' and 'then' - javascript

I'm trying to send a GET request, parse its response and return it to another method. Apparently I have problems handling the asynchronous response.
I want to use Node.js' standard modules, so no Axios.
// class 1: Calling API, processing and returning the response
export async function getData() {
let str = '';
const options = {
hostname: 'jsonplaceholder.typicode.com',
path: '/posts/',
method: 'GET',
json: true,
};
https
.get(options, response => {
response.on('data', chunk => {
str += chunk;
});
response.on('end', () => {
return parseJson(str);
});
})
.on('error', error => {
console.log(error);
});
}
async function parseJson(str) {
const json = JSON.parse(str);
var text;
try {
json.forEach(element => {
text += element.body;
});
// console.log(text); // I'm getting the expected output
return text;
} catch (error) {
console.log('error');
}
}
// class 2: Calling the 2 methods above
getData().then(function (value) {
console.log('DATA: ' + value); // this is called first
});
Unfortunately as output I get an undefined. Despite using async and then:
DATA: undefined

Change getData as follows
export function getData() {
return new Promise((resolve, reject) => {
let str = '';
const options = {
hostname: 'jsonplaceholder.typicode.com',
path: '/posts/',
method: 'GET',
json: true,
};
https.get(options, response => {
response.on('data', chunk => {
str += chunk;
});
response.on('end', () => {
try {
const result = parseJson(str);
resolve(result);
} catch (error) {
reject(error);
}
});
response.on('error', reject);
})
.on('error', reject);
});
}
Now it returns a Promise, which resolves to the result of parseJson(str) or rejects with the error at on('error'
And parseJson as follows - doesn't need to be async since there's nothing asynchronous about the code inside it
Also, removing the try/catch in parseJson and using try/catch in .on("end" means that you can reject the promise returned by getData if there's an error in the parseJson call
function parseJson(str) {
const json = JSON.parse(str);
let text = '';
json.forEach(element => {
text += element.body;
});
return text;
}
alternatively (IMHO better)
function parseJson(str) {
const data = JSON.parse(str); // call it data, not json
return data.map(({body}) => body).join('');
}
or even
const parseJson = (str) => JSON.parse(str).map(({body}) => body).join('');
But that's not important :p

Related

async/await in javascript class method with htpps call inside

I tried to make an https call and return the body containing the data, but when I tried to print it in the console it returned undefined so I used the "promises".
const https = require('https');
const baseUri = "https://apiv2.gofile.io/"
class apiGofile {
constructor(email,apikey) {
this.email=email;
this.apikey=apikey;
}
getBestServer() {
return new Promise((resolve,reject)=>{
https.get(baseUri + "getServer", (res) => {
let body="";
res.on("data", (data) => {
body += data;
});
res.on("end", () => {
body = JSON.parse(body);
resolve(body.data.server);
});
res.on("error",(e)=>{
reject(e);
});
});
});
};
}
let a = new apiGofile("a","b");
a.getBestServer()
.then(
response => console.log(response),
error => console.log(error)
);
is there any way to use await and async in my code?
You can move the following code into an asynchronous main function then call that:
async function main() {
const a = new apiGoFile("a", "b");
// await the promise instead of using .then
const response = await a.getBestServer();
console.log(response);
// you can now use response here
}
// make sure to call the function
main();
You can read more about async/await here.
You can also make class methods async:
class Foo {
async myMethod() {
const a = new apiGoFile("a", "b");
const response = await a.getBestServer();
console.log(response);
}
}

Set returned object of https.get to variable

Looking to set the object that is retrieved from a https.get to a variable opposed to printing it to console. Attempting to use Promises to achieve this but it is just returning the function itself.
const https = require("https");
if(true){
const destinationsfinal = () => {
return new Promise((resolve,reject) => {
https
.get('https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY', (resp) => {
let data = '';
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
resolve(JSON.parse(data))
});
})
.on('error', (err) => {
reject('Error: ' + err.message);
});
})
}
}
You have to resolve the promise in order to get the result from your function (that is because it returns a Promise and not the result of this promise). To solve this problem you can do it either with then/catch or async/await.
then/catch:
let result;
destinationsfinal()
.then((data) => {
result = data;
})
.catch((error) => {
console.log(error);
});
async/await:
let result;
(async () => {
try {
result = await destinationsfinal();
} catch (error) {
console.log(error);
}
})();

I wanna send string that is from s3 object to a client

I have a problem to send string that is from s3 object body to a client
I'm using aws sdk for node, and apollo server(express), express, react
I did get object from s3 and create readable stream. and then i listen data event so I might send string to a client
let data = '';
s3.getObject(params).createReadStream().on('data', function(chunk) {
data += chunk;
});
return { data }
I thought data is not a empty string but it is empty string
what can I do to solve the problem?
Edit:
let data = '';
function promiseBasedRequest (params) {
return new Promise((resolve, reject) => {
s3.getObject(params).createReadStream()
.on('data', function (chunk) {
data += chunk;
})
.on('end', function () {
resolve(data);
})
.on('error', function (err) {
reject(err);
});
});
}
await promiseBasedRequest(params);
This works as I intended.
You are not waiting for the writing to end. First the function need to be asynchronous a promise or callback.
function getData(params) {
let data = ''
return new Promise((res, rej) => {
let data = '';
s3.getObject(params).createReadStream()
.on('data', function (chunk) {
data += chunk;
})
.on('end', function(){
res(data);
})
.on('error', function(){
rej()
})
})
}
You can use the function by:
(async(){
const data = await getData()
})();
Or getData().then(..)
EDIT: Also, getObject has one promise method as well.
s3.getObject(params).promise().then(...).catch(...)

Calling async function multiple times

So I have a method, which I want to call multiple times in a loop. This is the function:
function PageSpeedCall(callback) {
var pagespeedCall = `https://www.googleapis.com/pagespeedonline/v4/runPagespeed?url=https://${websites[0]}&strategy=mobile&key=${keys.pageSpeed}`;
// second call
var results = '';
https.get(pagespeedCall, resource => {
resource.setEncoding('utf8');
resource.on('data', data => {
results += data;
});
resource.on('end', () => {
callback(null, results);
});
resource.on('error', err => {
callback(err);
});
});
// callback(null, );
}
As you see this is an async function that calls the PageSpeed API. It then gets the response thanks to the callback and renders it in the view. Now how do I get this to be work in a for/while loop? For example
function PageSpeedCall(websites, i, callback) {
var pagespeedCall = `https://www.googleapis.com/pagespeedonline/v4/runPagespeed?url=https://${websites[i]}&strategy=mobile&key=${keys.pageSpeed}`;
// second call
var results = '';
https.get(pagespeedCall, resource => {
resource.setEncoding('utf8');
resource.on('data', data => {
results += data;
});
resource.on('end', () => {
callback(null, results);
});
resource.on('error', err => {
callback(err);
});
});
// callback(null, );
}
var websites = ['google.com','facebook.com','stackoverflow.com'];
for (let i = 0; i < websites.length; i++) {
PageSpeedCall(websites, i);
}
I want to get a raport for each of these sites. The length of the array will change depending on what the user does.
I am using async.parallel to call the functions like this:
let freeReportCalls = [PageSpeedCall, MozCall, AlexaCall];
async.parallel(freeReportCalls, (err, results) => {
if (err) {
console.log(err);
} else {
res.render('reports/report', {
title: 'Report',
// bw: JSON.parse(results[0]),
ps: JSON.parse(results[0]),
moz: JSON.parse(results[1]),
// pst: results[0],
// mozt: results[1],
// bw: results[1],
al: JSON.parse(results[2]),
user: req.user,
});
}
});
I tried to use promise chaining, but for some reason I cannot put it together in my head. This is my attempt.
return Promise.all([PageSpeedCall,MozCall,AlexaCall]).then(([ps,mz,al]) => {
if (awaiting != null)
var areAwaiting = true;
res.render('admin/', {
title: 'Report',
// bw: JSON.parse(results[0]),
ps: JSON.parse(results[0]),
moz: JSON.parse(results[1]),
// pst: results[0],
// mozt: results[1],
// bw: results[1],
al: JSON.parse(results[2]),
user: req.user,
});
}).catch(e => {
console.error(e)
});
I tried doing this:
return Promise.all([for(let i = 0;i < websites.length;i++){PageSpeedCall(websites, i)}, MozCall, AlexaCall]).
then(([ps, mz, al]) => {
if (awaiting != null)
var areAwaiting = true;
res.render('admin/', {
title: 'Report',
// bw: JSON.parse(results[0]),
ps: JSON.parse(results[0]),
moz: JSON.parse(results[1]),
// pst: results[0],
// mozt: results[1],
// bw: results[1],
al: JSON.parse(results[2]),
user: req.user,
});
}).catch(e => {
console.error(e)
});
But node just said it's stupid.
And this would work if I didn't want to pass the websites and the iterator into the functions. Any idea how to solve this?
To recap. So far the functions work for single websites. I'd like them to work for an array of websites.
I'm basically not sure how to call them, and how to return the responses.
It's much easier if you use fetch and async/await
const fetch = require('node-fetch');
async function PageSpeedCall(website) {
const pagespeedCall = `https://www.googleapis.com/pagespeedonline/v4/runPagespeed?url=https://${website}&strategy=mobile&key=${keys.pageSpeed}`;
const result = await fetch(pagespeeddCall);
return await result.json();
}
async function callAllSites (websites) {
const results = [];
for (const website of websites) {
results.push(await PageSpeedCall(website));
}
return results;
}
callAllSites(['google.com','facebook.com','stackoverflow.com'])
.then(results => console.log(results))
.error(error => console.error(error));
Which is better with a Promise.all
async function callAllSites (websites) {
return await Promise.all(websites.map(website => PageSpeedCall(website));
}
Starting on Node 7.5.0 you can use native async/await:
async function PageSpeedCall(website) {
var pagespeedCall = `https://www.googleapis.com/pagespeedonline/v4/runPagespeed?url=https://${website}&strategy=mobile&key=${keys.pageSpeed}`;
return await promisify(pagespeedCall);
}
async function getResults(){
const websites = ['google.com','facebook.com','stackoverflow.com'];
return websites.map(website => {
try {
return await PageSpeedCall(website);
}
catch (ex) {
// handle exception
}
})
}
Node http "callback" to promise function:
function promisify(url) {
// return new pending promise
return new Promise((resolve, reject) => {
// select http or https module, depending on reqested url
const lib = url.startsWith('https') ? require('https') : require('http');
const request = lib.get(url, (response) => {
// handle http errors
if (response.statusCode < 200 || response.statusCode > 299) {
reject(new Error('Failed to load page, status code: ' + response.statusCode));
}
// temporary data holder
const body = [];
// on every content chunk, push it to the data array
response.on('data', (chunk) => body.push(chunk));
// we are done, resolve promise with those joined chunks
response.on('end', () => resolve(body.join('')));
});
// handle connection errors of the request
request.on('error', (err) => reject(err))
})
}
Make PageSpeedCall a promise and push that promise to an array as many times as you need, e.g. myArray.push(PageSpeedCall(foo)) then myArray.push(PageSpeedCall(foo2)) and so on. Then you Promise.all the array.
If subsequent asynch calls require the result of a prior asynch call, that is what .then is for.
Promise.all()
Promise.all([promise1, promise2, promise3]).then(function(values) {
console.log(values);
});

Can't get an object from a get request

I can console log userData.name and userData.summonerLevel just fine, but why can't I return userData as an object?
I am trying to assign the returned object to a variable, but I really don't understand why it doesn't work.
function getStats() {
https.get(https://euw1.api.riotgames.com/lol/summoner/v3/summoners/by-name/yojimbozx?api_key=${API_KEY},
(res) => {
let userData = ''
res.on('data', (chunk) => {
userData += chunk
})
res.on('end', () => {
userData = JSON.parse(userData)
if(!userData.hasOwnProperty('status')) {
console.log(userData.name, userData.summonerLevel)
return userData
}
return console.log("Summoner not found")
})
}).on("error", (err) => {
return console.log("Error: " + err.message)
})
}
You can, but since you are doing some async stuff you should have your function getStats () return the http call and it should work. Currently you are returning the outcome of the call to the function, but the function is not returning anything

Categories

Resources