I have copied the very good code from https://www.tomas-dvorak.cz/posts/nodejs-request-without-dependencies/ to make a http request in nodejs using native modules.
I want to be able to use the data value later on in the script.
I know this is a common issue with newbies and async code, i just CANNOT understand this yet and have struggled for weeks to get it.
I have coped much code, watched youtube, talked to people, its flippen hard..
const getContent = function(url) {
return new Promise((resolve, reject) => {
const https = require('https')
const request = https.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))
})
}
getContent('https://myapi/json')
.then((data) => console.log(data))
.catch((err) => console.error(err))
// I want to use the "data" value down here in my script. I want to do things with the "data" value like JSON.parse(data)
console.log(data) //undefined
let json = JSON.parse(data) //undefined
console.log('after')
my result for data is undefined
How can i use data down here below all the code above?
You can setup a callback and access your data within this callback, this pattern should be easy enough to use.
getContent('https://myapi/json')
.then(useData)
.catch((err) => console.error(err))
// Use this callback to do what you want with your data!
function useData(data) {
console.log(data);
let json = JSON.parse(data);
}
Or using async / await ( this might be more intuitive!):
async function testAwait() {
let data = await getContent('https://myapi/json');
console.log("data: ", data);
}
testAwait();
Related
I am trying the whole day to get some https request running.
My code so far does not work, after calling it, i am running in an "Unhandled error RangeError: Maximum call stack size exceeded at Function.entries"
import * as https from "https";
function openRequest(options : any)
{
return new Promise((resolve, reject) => {
const request = https.request(options).on('response', (response : any) => {
resolve(response);
}).on('error', (error : any) => {
reject(error);
});
request.end();
});
}
I have to use a default library, so another one won't do the work.
Can someone tell me where i am dooing something wrong?
I've got this stuff to run, typescript isn't my native language and i really don't won't to make my living out of it but some stranger in the internet used to "await for" the res object in a loop:
const request = https.request(options, async (res : any) => {
res.setEncoding("utf-8");
let result = "";
for await (const chunk of res)
{
result += chunk;
}
resolve(result);
}).on("error", (error) => {
reject(error);
});
request.end();
I really don't know, if on("error"...) will work, because .on("response"...) has failed so far. But at least, in a good day at full moon, this code runs.
I am trying to use an API to get IP address that gets a local city. Then using the city name from the IP address to run threw another API to get the local weather when they visit my cite. This is my first NodeJs project I am attempting on my own. I wanted one API call to get a JSON object that contains the info of the IP address. So far I have only gotten undefined or Promise {pending}.
I have looked up using async/await functions and using the fetch() function instead of https.
I have also tried wrapping the get request around a new Promise function but still with undefined results. In this project I have the main app.js and a functions.js in attempt to keep clean code. I am completely lost.
first try.
function getRawData(){
const https = require('https');
return new Promise((resolve, reject) =>{
https.get('https://api.ipgeolocation.io/ipgeo?apiKey=' + ipApiKey, res => {
console.log('statusCode:', res.statusCode);
res.on('data', d => {
let data = JSON.parse(d);
let cData = data.city
resolve(cData);
}).on('error', e =>{
reject('error'. e.message);
});
});
});
}
function getIp(){
getRawData().then(data =>{
return data;
}).catch(error => {
console.log(error);
});
}
A different type of code I tried:
async function getIPAddress(){
let data = await fetch('https://api.ipgeolocation.io/ipgeo?apiKey=' + ipApiKey)
.then(res => res.json())
.then(res => {return res});
return data;
}
when I would call any of the functions on the main app.js I would not get the JSON data I need. Please help.
I found my answer using the fetch function in a stacked call.
function requestIP(url){
return fetch(url)
.then(r => r.json())
.then(json => json);
}
fs.requestIP(ipUrl).then(data =>{
const part ='hourly,alerts,minutely';
const lat = data.lat;
const lon = data.lon;
const name = data.city;
const apiKey = process.env.WEATHER_API_KEY;
const units = "imperial";
const urlWeather = 'https://api.openweathermap.org/data/2.5/onecall?lat=' + lat + '&lon=' + lon + '&units=' + units + '&exclude=' + part + '&appid=' + apiKey;
fetch(urlWeather)
.then(r => r.json())
.then(json =>{
let todayW = json.current;
let tomW = json.daily[0];
let n1Day = json.daily[1];
let n2Day = json.daily[2];
let todayDate = fs.todaysDate();
res.render('home', {
CR: fs.getCopyRights(),
dayDate: todayDate,
cityName: name,
todayT: Math.floor(todayW.temp),
todayI: todayW.weather[0].icon,
tomT: Math.floor(tomW.temp.day),
tomI: tomW.weather[0].icon,
next1T: Math.floor(n1Day.temp.day),
next1I: n1Day.weather[0].icon,
next2T: Math.floor(n2Day.temp.day),
next2I: n2Day.weather[0].icon,
});
});
});
apollo-server-micro package tries to receive request body stream that already received before(in some scenarios) and hangs on forever cause never receives events of stream(it already fully obtained)
I gone step by step through all the flow to discover the issue.
In brief the issue pops up when request stream that passed to Apollo already read before. It means we already had used for example: body.on('data', onData) & body.on('end', onEnd) or it was executed by another chain in the flow(express server, next.js server, firebase cloud function).
And if it was before what is going in apollo-server-micro code is that it tries to do it again, but it will never occur and we will fail on timeout or never get the response, because: body.on('data', or body.on('end' will never be called again(the stream already parsed before fully, these event will not happen).
So I think some way is needed to treat this situation and to give Apollo option to work with body stream already received. May be we need some way to say Apollo to not try to receive body stream if it already exists and just deliver it already prepared(buffer) by some property. So we don't need to do it if I already can provide it to the Apollo.
I found some hack I can do, but by far it needed to be done in more proper way.
Apollo uses json function from micro package (https://github.com/vercel/micro) to get the body stream. if I change this line there:
const body = rawBodyMap.get(req);
to something like:
const body = rawBodyMap.get(req) || req.rawBody;
I have rawBody because I use firebase cloud function and when it receives body stream it saves received stream buffer in rawBody property of request (and it's exactly what json function of micro tries to achieve).
full flow:
src/ApolloServer.ts ( from apollo-server-micro package )
import { graphqlMicro } from './microApollo';
const graphqlHandler = graphqlMicro(() => {
return this.createGraphQLServerOptions(req, res);
});
const responseData = await graphqlHandler(req, res);
send(res, 200, responseData);
microApollo.ts - we use here json function from 'micro' passing req as parameter
import { send, json, RequestHandler } from 'micro'; ( https://github.com/vercel/micro )
const graphqlHandler = async (req: MicroRequest, res: ServerResponse) => {
let query;
try {
query =
req.method === 'POST'
? req.filePayload || (await json(req))
: url.parse(req.url, true).query;
} catch (error) {
// Do nothing; `query` stays `undefined`
}
https://github.com/vercel/micro package
const getRawBody = require('raw-body');
exports.json = (req, opts) =>
exports.text(req, opts).then(body => parseJSON(body));
exports.text = (req, {limit, encoding} = {}) =>
exports.buffer(req, {limit, encoding}).then(body => body.toString(encoding));
exports.buffer = (req, {limit = '1mb', encoding} = {}) =>
Promise.resolve().then(() => {
const type = req.headers['content-type'] || 'text/plain';
const length = req.headers['content-length'];
// eslint-disable-next-line no-undefined
if (encoding === undefined) {
encoding = contentType.parse(type).parameters.charset;
}
// my try to hack the behavior
const body = rawBodyMap.get(req) || req.rawBody;
console.log(">>>>>>>>>>>>>>>>>>> ", body);
if (body) {
return body;
}
return getRawBody(req, {limit, length, encoding})
.then(buf => {
rawBodyMap.set(req, buf);
return buf;
})
.catch(err => {
if (err.type === 'entity.too.large') {
throw createError(413, `Body exceeded ${limit} limit`, err);
} else {
throw createError(400, 'Invalid body', err);
}
});
});
if I don't stop by my hack the code from going to receive the body
stream it calls to : getRawBody from 'raw-body' package;
raw-body package
function getRawBody (stream, options, callback) {
……
return new Promise(function executor (resolve, reject) {
readStream(stream, encoding, length, limit, function onRead (err, buf) {
if (err) return reject(err)
resolve(buf)
})
})
}
function readStream (stream, encoding, length, limit, callback) {
…….
// attach listeners
// these callbacks never called because body request stream already received before
stream.on('aborted', onAborted)
stream.on('close', cleanup)
stream.on('data', onData)
stream.on('end', onEnd)
stream.on('error', onEnd)
…….
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.
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.