This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 3 years ago.
I have created a nodejs express application using Express Generator.
In its route index.js I'm trying to make a rest api call using default HTTP module in the standard library https://nodejs.org/api/http.html#http_http_get_options_callback (I do not want to install external dependencies)and that would provide the values for title and welcome text in the view. Below is the code for index.js.
var express = require('express');
var router = express.Router();
const http = require('http');
/* GET home page. */
router.get('/', function(req, res, next) {
let titletext = '';
let wtext = '';
http.get('http://localhost:5000/api/values', (apires) => {
const { statusCode } = apires;
const contentType = apires.headers['content-type'];
let error;
if (statusCode !== 200) {
error = new Error('Request Failed.\n' +
`Status Code: ${statusCode}`);
} else if (!/^application\/json/.test(contentType)) {
error = new Error('Invalid content-type.\n' +
`Expected application/json but received ${contentType}`);
}
if (error) {
console.error(error.message);
// Consume response data to free up memory
apires.resume();
return;
}
apires.setEncoding('utf8');
let rawData = '';
apires.on('data', (chunk) => { rawData += chunk; });
apires.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
console.log(parsedData);
titletext=parsedData[1];
wtext=parsedData[0];
} catch (e) {
console.error(e.message);
}
});
}).on('error', (e) => {
console.error(`Got error: ${e.message}`);
});
res.render('index', { title: titletext, data:wtext });
});
module.exports = router;
But it does not seem to be working and titletext and wtest were coming as empty strings. So, I added three breakpoints. One just after the get call, second after get call response comes and the last on the page response render call(res.render) at the very end.
Now when I run, I find that the get call breakpoint gets called,then the page response render call breakpoint gets called and finally the get call response breakpoint gets called, the data comes from the rest api but by then the page render call is already over and so the data from api call does not reach the view.
Any ideas how to solve this is sincerely appreciated
Http request call is async, so if you put res.render at the end of GET handler, it will render empty data before request returns response.
Following the document, you should parse response's body and render page from end event handler, like:
router.get('/', function(req, res, next) {
let titletext = '';
let wtext = '';
http.get('http://localhost:5000/api/values', (apires) => {
const { statusCode } = apires;
const contentType = apires.headers['content-type'];
let error;
if (statusCode !== 200) {
error = new Error('Request Failed.\n' +
`Status Code: ${statusCode}`);
} else if (!/^application\/json/.test(contentType)) {
error = new Error('Invalid content-type.\n' +
`Expected application/json but received ${contentType}`);
}
if (error) {
console.error(error.message);
// Consume response data to free up memory
apires.resume();
return;
}
apires.setEncoding('utf8');
let rawData = '';
apires.on('data', (chunk) => { rawData += chunk; });
apires.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
console.log(parsedData);
titletext=parsedData[1];
wtext=parsedData[0];
// Render page here
res.render('index', { title: titletext, data:wtext });
} catch (e) {
console.error(e.message);
}
});
}).on('error', (e) => {
console.error(`Got error: ${e.message}`);
});
// Not here
});
Related
This question is similar to this older question but I was not able to get the accepted answer to work correctly.
I am using the built-in NodeJS 'https' module to make requests to an external API. NodeJS version 12.
node: 12.16
express: 4.16.1
I was able to get it working with the example code from the documentation.
router.get('/', (req, res, next) => {
const requestOptions = httpCtrl.getReqOptions();
// Working example
// How to separate this logic into reusable function?
const request = https.request(requestOptions, (response) => {
let result = {
status: null,
data: {}
};
let rawData = '';
response.on('data', (chunk) => {
rawData += chunk;
});
response.on('end', () => {
console.log('No more data in response.');
try {
parsedData = JSON.parse(rawData);
result.status = parsedData.status || 200;
result.data = parsedData;
return res.status(result.status).json(result);
} catch (e) {
result.status = 500;
result.data.message = `ERROR: Unable to parse API response`;
result.data.exception = e;
return res.status(result.status).send(result);
}
});
});
request.on('error', (e) => {
result.status = 500;
result.data.message = `ERROR: API response`;
result.data.exception = e;
return res.status(result.status).send(result);
});
request.end();
});
However, I want to break out this logic into a reusable function, and just pass it the request options dynamically.
I tried just creating a synchronous function wrapper and returning the results, but obviously that didn't work because the sync function does not wait for the completion of the async request.
httpCtrl = {};
httpCtrl.createRequest = (requestOptions) => {
// Does not work due to being synchronous, also tried with async await to no avail
const request = https.request(requestOptions, (response) => {
let result = {
status: null,
data: {}
};
let rawData = '';
response.on('data', (chunk) => {
rawData += chunk;
});
response.on('end', () => {
console.log('No more data in response.');
try {
parsedData = JSON.parse(rawData);
result.status = parsedData.status || 200;
result.data = parsedData;
return result;
} catch (e) {
result.status = 500;
result.data.message = `ERROR: Unable to parse NRS Admin API response`;
result.data.exception = e;
return result;
}
});
});
request.on('error', (e) => {
result.status = 500;
result.data.message = `ERROR: API response`;
result.data.exception = e;
return result;
});
request.end();
});
}
router.get('/', (req, res, next) => {
const requestOptions = httpCtrl.setRequestOptions();
const result = httpCtrl.createRequest(requestOptions);
return res.status(result.status).send(result);
});
How can I update this code to be more re-usable?
Transform createRequest function to a promise, promises work like callbacks except they are much better to read.
// *** createReuqest function is a Promise ***
httpCtrl.createRequest = (requestOptions) => {
return new Promise((resolve, reject) => {
const result = {};
// *** http.request function is a Callback ***
const request = http.request(requestOptions, response => {
let rawData = '';
response.on('data', chunk => rawData += chunk);
// resolve the promise when response ends
response.on('end', () => {
result.status = response.status || 200;
result.data = rawData;
resolve(result);
});
});
// or reject on error
request.on('error', e => {
result.status = 500;
result.data = {
message: 'ERROR: API response',
exception: e
};
reject(result);
});
request.end();
});
};
Now we simply call the function and we chain it with then and catch, however, I choose to use async/await to include all asynchronous JavaScript in this example :) async/await is based on promises but with even cleaner markup.
// *** Finally async/await ***
router.get('/', async (req, res) => {
// initial options for testing
const requestOptions = {
hostname: 'www.google.com',
port: 443,
method: 'GET'
};
// await must be in try/catch to properly handle promise's resolve/reject
try {
const response = await httpCtrl.createRequest(requestOptions);
res.status(response.status).send(response.data);
} catch (error) {
res.status(error.status).send(error.data);
}
});
Hope I've helped.
I want to write code for a server but outsource http get routines to a separate .js file. When I try to call the function within the server.js file I get the error "not a function"
To check my general setup I created a dummy function which I call within server.js. This appears to work fine
Server.js:
const http = require("http");
const MSWratings = require('./MSWratings.js');
MSWratings.Ratings();
MSWratings.TestFunction();
MSWratings.js:
//Load HTTP module
const http = require("http");
const TestFunction = function () {
console.log('test function');
}
const Ratings = function () {
http.get('XXX', (res) => {
const { statusCode } = res;
const contentType = res.headers['content-type'];
let error;
if (statusCode !== 200) {
error = new Error('Request Failed.\n' +
`Status Code: ${statusCode}`);
} else if (!/^application\/json/.test(contentType)) {
error = new Error('Invalid content-type.\n' +
`Expected application/json but received ${contentType}`);
}
if (error) {
console.error(error.message);
// consume response data to free up memory
res.resume();
return;
}
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk) => { rawData += chunk; });
res.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
console.log(parsedData[0].solidRating);
//console.log(parsedData);
} catch (e) {
console.error(e.message);
}
});
}).on('error', (e) => {
console.error(`Got error: ${e.message}`);
});
}
module.exports = {Ratings};
module.exports = {TestFunction};
The get request does not run, I get the error
TypeError: MSWratings.Ratings is not a function
instead
You are overwriting your exports the second time you assign to it.
Try
module.exports = {Ratings, TestFunction};
I'm trying to find if there is a way to simulate a visit in a website like I was in web browser, with javascript execution etc...
If I try like with jsdom, google analytics shows me nothing for my personnel website. It's only capture source code.
How can I do that with node.js ?
What do you mean by simulate? When a web browser visits a website, it makes an http request to the server, and if the server returns an html document, it parses and shows it to you. You could do an http request using Node.js http module (example taken directly from docs):
http.get('http://nodejs.org/dist/index.json', (res) => {
const { statusCode } = res;
const contentType = res.headers['content-type'];
let error;
if (statusCode !== 200) {
error = new Error('Request Failed.\n' +
`Status Code: ${statusCode}`);
} else if (!/^application\/json/.test(contentType)) {
error = new Error('Invalid content-type.\n' +
`Expected application/json but received ${contentType}`);
}
if (error) {
console.error(error.message);
// Consume response data to free up memory
res.resume();
return;
}
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk) => { rawData += chunk; });
res.on('end', () => {
try {
const parsedData = JSON.parse(rawData);
console.log(parsedData);
} catch (e) {
console.error(e.message);
}
});
}).on('error', (e) => {
console.error(`Got error: ${e.message}`);
});
I write API in order to client upload file. API has content-type multiple/form-data. But I don't know get values from client send to my
router.post('/upload/file', async (req, res) => {
var body = "";
try {
req.on('data', function (chunk) {
body += chunk;
});
req.on('end', function () {
console.log('body: ' + body);
var formData = new FormData(body);
console.log("=====================", formData.entries);
// var {accessTok, type, file} = req.params;
//
// if (!accessTok || !type || !file) {
res.json({
code: -1000,
message: 'Missing parameter(s). Please provide accessToken, type upload, file upload.'
});
res.end();
return null;
})
// }
}catch(err){
res.json({err: err.message});
res.end();
return;
}
I tried use FormData but not done. I get error is not function, formData.getKey('') is them same.
I'm fumbling my way through node.js with massive help from people on here and I'm struggling getting the body of a GET request into a variable.
Here's the code so far:
var speechOutput;
var myCallback = function(data) {
console.log('got data: '+data);
speechOutput = data;
};
var usingItNow = function(callback) {
var http = require('http');
var url = 'http://services.groupkt.com/country/get/iso2code/IN';
var req = http.get(url, (res) => {
var body = "";
res.on("data", (chunk) => {
body += chunk;
});
res.on("end", () => {
var result = JSON.parse(body);
callback(result);
});
}).on('error', function(e){
console.log("Got an error: ", e);
});
};
usingItNow(myCallback);
I'm using examples from other posts to try and get the body of the GET request into the speechOutput variable but it is coming out as undefined.
Ultimately I want the RestResponse.result.name in speechOutput, but I thought I would take this one step at a time. Can anyone offer any pointers?
Further to this, I have tried the following, which still came back undefined - maybe there is a bigger issue with the code? It doesn't even seem to be getting to the parse.
res.on("end", () => {
// var result = JSON.parse(body);
callback('result');
});
putting the line callback('result'); before the line var req = http.get(url, (res) => { returns 'result' but anything else is either undefined or causes an error.
Quoting Roy T. Fielding:
Server semantics for GET, however, are restricted such that a body,
if any, has no semantic meaning to the request. The requirements
on parsing are separate from the requirements on method semantics.
Don't use get request to send body parameters. Use post requests. If you want to send data within a get request, add them to the query string.
Read this for more info about bodies in get requests:
HTTP GET with request body
Update:
Try to log errors in the response, add this before you set up the listeners:
var body = "";
const { statusCode } = res;
const contentType = res.headers['content-type'];
let error;
if (statusCode !== 200) {
error = new Error('Request Failed.\n' +
`Status Code: ${statusCode}`);
} else if (!/^application\/json/.test(contentType)) {
error = new Error('Invalid content-type.\n' +
`Expected application/json but received ${contentType}`);
}
if (error) {
console.error(error.message);
// consume response data to free up memory
res.resume();
return;
}
res.on("data", (chunk) => {
body += chunk;
});