I am trying to access what a function returns in node.js
I have the following function:
function getImg(callback) {
https.get('https://api.nasa.gov/planetary/apod?api_key=api-key', response => {
let data = "";
response.on('data', chunk => {
data += chunk;
});
response.on('end', () => {
let img = JSON.parse(data).hdurl;
callback(null, img);
})
}).end();
}
let image = getImg(function(err, image) {
console.log(image);
})
res.render('index', {
indexCSS: true,
image
})
It can log it to the console correctly, but if I want to access the value of the variable like I do in the last line of my code or if I console.log(image) I get undefined.
What have I done wrong. How can I access what the function produces?
It is a callback style function which wouldn't return any thing. Better to convert it to promise sttyle and use async/await to get the value in a variable
function getImg() {
return new Promise((resolve, reject) => {
https.get('https://api.nasa.gov/planetary/apod?api_key=api-key', response => {
let data = "";
response.on('data', chunk => {
data += chunk;
});
response.on('end', () => {
let img = JSON.parse(data).hdurl;
resolve(img);
})
}).end();
});
}
(async() => {
let image = await getImg();
res.render('index', {
indexCSS: true,
image
});
})();
You can't really store the return value of your function like that. Unfortunately JS is non-blocking, so the code will continue to execute past it, before it has a chance to return that value from the https request. I am not sure exactly when you call this function, but you could call res.render in the callbacks response after calling getImg() without assigning its value to something. You can use promises, otherwise it's better to handle the response you need when it is returned from the callback. That would just be a simple call like:
getImg(function(err, image) {
res.render('index', {
indexCSS: true,
image
});
})
Within whatever route is calling this function. You just cannot assign any kind of return value from a callback to a variable (really not recommended at least) in the normal way.
Related
ok i saw this example of reading a stream and returning a promise using new Promise.
function readStream(stream, encoding = "utf8") {
stream.setEncoding(encoding);
return new Promise((resolve, reject) => {
let data = "";
stream.on("data", chunk => data += chunk);
stream.on("end", () => resolve(data));
stream.on("error", error => reject(error));
});
}
const text = await readStream(process.stdin);
My question is why "new Promise" ? can i do it in the 2nd version like
function readStream(stream, encoding = "utf8") {
stream.setEncoding(encoding);
let data = "";
stream.on("data", chunk => data += chunk);
stream.on("end", () => Promise.resolve(data));
stream.on("error", error => Promise.reject(error));
}
const text = await readStream(process.stdin);
Haven't tried it yet, but basically want to avoid the new keyword.
some updates on the 2nd version, since async functions always return a Promise.
A function/method will return a Promise under the following circumstances:
You explicitly created and returned a Promise from it's body.
You returned a Promise that exists outside the method.
You marked it as async.
const readStream = async (stream, encoding = "utf8") => {
stream.setEncoding(encoding);
let data = "";
stream.on("data", chunk => data += chunk);
stream.on("end", () => Promise.resolve(data));
stream.on("error", error => Promise.reject(error));
}
const text = await readStream(process.stdin);
How's this 3rd version ?
If you want readStream to return a promise, you'll have to ... return a promise for readStream (returning a promise in some callback is not doing that).
What the first code is doing, is promisifying the stream API. And that's exactly how it should be done.
The second version of the code is based on a misunderstanding: it seems to hope that returning a promise in the callback passed to the stream.on method, will somehow make readStream return that promise. But when the on callback is called, readStream has already returned. Since readStream has no return statement, it already returned undefined and not a promise.
As a side note, when the stream API calls the callback you passed to the on method, it does not even look at the returned value -- that is ignored.
The third version is an async function, so it now is guaranteed the function will return a promise. But as the function still does not execute a return statement, that promise is immediately resolved with value undefined. Again, the returned values in the callbacks are unrelated to the promise that the async function has already returned.
new keyword
If you want to avoid the new keyword, then realise that anything that can be done with promises can also be done without them. In the end promises are "only" a convenience.
For instance, you could do:
function readStream(stream, success, failure, encoding="utf8") {
let data = "";
stream.setEncoding(encoding);
stream.on("data", chunk => data += chunk);
stream.on("end", () => success(data));
stream.on("error", failure);
}
function processText(text) {
// ... do something with text
}
function errorHandler(error) {
// ... do something with the error
}
readStream(process.stdin, processText, errorHandler);
In typical Node style you would pass one callback, for both purposes, as last argument:
function readStream(stream, encoding="utf8", callback) {
let data = "";
stream.setEncoding(encoding);
stream.on("data", chunk => data += chunk);
stream.on("end", () => callback?.(null, data));
stream.on("error", err => callback?.(err, null));
}
function processText(err, text) {
if (err) {
// do something with err
return;
}
// ... do something with text
}
readStream(process.stdin, "utf8", processText);
And then you could use the util package to turn that into a promise-returning function:
const util = require('util');
const readStream = util.promisify(function (stream, encoding="utf8", callback) {
let data = "";
stream.setEncoding(encoding);
stream.on("data", chunk => data += chunk);
stream.on("end", () => callback?.(null, data));
stream.on("error", err => callback?.(err, null));
});
(async () => {
try {
const text = await readStream(stream, "utf8");
// do something with text
} catch(err) {
// do something with err
}
})();
Of course, under the hood the promisfy function performs new Promise and we're back to where we started.
You need to construct and return a Promise so that the consumer of the function has something to hook into the asynchronous action being performed. (Another option would be to define the function to also take a callback as an argument.)
If you try to do it the way you're doing with the second snippet, readStream will not return anything, so await readStream(process.stdin); will resolve immediately, and it'll resolve to undefined.
Doing
stream.on("end", () => Promise.resolve(data));
and
stream.on("error", error => Promise.reject(error));
constructs new Promises at that point in the code, but you need the consumer of the function to have access to the Promise that resolves (or rejects) - and so you must have return new Promise at the top level of the function.
I am trying to process signup data for a uni project . I am using basic koa modules and I am not allowed to use express, ideally I want to get the data inside the variable post. I want to process the data for example to see if the password has less than 5 characters , if so i would like that the program would not redirect the user to different address but if no errors occur i would like the program to redirect to regOk.html, I tried many other ways like initializing the variable outside of ctx.req.on but none were successful . Can anyone help me ?
export async function postregister(ctx) {
let bodyString = "";
ctx.req.on("data", (chunk) => {
bodyString += chunk;
});
//let collectData = new Array();
ctx.req.on("end", () => {
var post = querystring.parse(bodyString);
var email = post["email"];
var password = post["password"];
var passbestätigen = post["passwort bestä"];
var vorname = post["vorname"];
var nachname = post["nachname"];
var adresse = post["adresse"];
var stadt = post["stadt"];
var telefonnummer = post["telefonnummer"];
var geburtsdatum = post["geburtsdatum"];
var regData = model.add(ctx.db, post);
regData.then(() => console.log("singup successful"))
});
await ctx.render("regOk.html");
}
I'm not very familiar with koa, but I believe your issue is related to the order in which your code is executed.
The event in charge of parsing the data received in the body of the request ends after the synchronic execution of your postregister method, so you never get to see the value of post in the order you'd expect.
One possible solution to go around this issue would be wrapping the parsing of data in a promise, waiting for that promise to complete, and executing then and catch functions once the processing is done.
export async function postregister(ctx) {
await new Promise((resolve) => {
let bodyString = "";
ctx.req.on("data", (chunk) => {
bodyString += chunk;
});
ctx.req.on("end", async () => {
resolve(querystring.parse(bodyString));
});
})
.then(async (post) => {
await model.add(ctx.db, post)
.then(async () => {
console.log("singup successful");
await ctx.render('regOk.html');
});
})
.catch(async (error) => {
console.error(error);
await ctx.render('error.html');
});
}
This way, you handle body parsing inside the Promise, and after that completed you get the result of querystring.parse(bodyString) as a variable named post in your then handler.
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();
I saw the the request library was depreciated, so I have been trying to switch to Node's https method instead. I pieced together this basic request function so far.
const https = require('https')
function httpRequest(options) {
return new Promise((resolve, reject) => {
const serverRequest = https.request(options, response => {
let body = ''
response.on('data', function (d) {
body += d
});
response.on('end', function () {
resolve(JSON.parse(body))
})
})
serverRequest.on('error', err => {
reject(err)
})
serverRequest.end()
})
}
It works, but causes eslint to throw prefer-arrow-callback. I don't fully understand why https uses the .on syntax in the first place, so I'm wondering if this function can be re-written in a way that gets rid of the warning and is more in line with modern JavaScript.
I believe that that error means to say it would prefer a Lambda function definition. If you are new to lambda functions, they are formatted as such:
(parameters) => {
}
Try re-writing your code like this:
response.on('data', (d) => {
body += d;
});
response.on('end', () => {
resolve(JSON.parse(body));
});
As for the use of .on, its just how Node formats event listeners.
I am trying to write my first Alexa Skill and I am doing an http request which works fine, however I would like to wrap this into a function that I can call from my main code.
It is not working and I am not quite sure how I can store the response into a variable, here is my code so far:
function getValue(loc) {
var endpoint = 'URLHERE'
var something = ""
var body = ""
https.get(endpoint, (response) => {
response.on('data', (chunk) => {
body += chunk
})
response.on('end', () => {
data = JSON.parse(body)
something = data.result.node.value;
})
})
return something;
}
This is on amazon and using lambda for the functions using node.js
which I call getValue('test') using
var result = getValue('test')
it just returned undefined.
Any idea's? Thanks
I imagine because its an asynchronous call its not setting the value due to callback but I have tried implementing this and cannot get it to work.
Thanks
You're returning from your function before the callback of http.get, mixing Synchronous with Asynchronous behaviour.
So your function is returning something which hasn't been defined yet.
Try this instead :
function getValue(loc,cb) {
let endpoint = 'URLHERE'
let something = ""
let body = ""
https.get(endpoint, (response) => {
response.on('data', (chunk) => {
body += chunk
})
response.on('end', () => {
data = JSON.parse(body)
cb(data.result.node.value);
})
})
}
getValue(test,(result)=>{
//do something with result here;
});