Wait for response from node.js request using await - node.js

Yes, I have seen many other questions and answers. I know I need to use a callback response. However, I still don't get how to do this particular example. Most examples involve a callback response that logs something or the post has hundreds of different answers.
How do I return the request response from getPageData?
var url = "myurl";
var name = await getPageData(url);
// wait until I get name and then do stuff with name
function getPageData(url)
{
const https = require('https');
https.get(url, (resp) => {
let data = '';
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
var name = JSON.parse(data);
// what do I do here to get name out?
});
}).on("error", (err) => {
console.log("Error: " + err.message);
});
}

await can only be used in async functions. You can however return a promise from getPageData and "await" using chained then:
Use the Promise object:
const https = require('https');
var url = "myurl";
var name;
getPageData(url)
.then(data => { name = data; /*This is the scope in which you would use name*/ })
.catch(err => { console.log('Error occured', err); });
async function getPageData(url) {
return new Promise((resolve, reject) => {
https
.get(url, resp => {
let data = '';
resp.on('data', chunk => {
data += chunk;
});
resp.on('end', () => {
const name = JSON.parse(data);
// what do I do here to get name out?
resolve(name);
});
})
.on('error', err => {
console.log(`Error: ${err.message}`);
reject(err);
});
});
}

The higher level solution here is to use a module for making http requests that already supported promises. You can see a list of many of them here. My favorite from that list is got() and you can use it to solve your problem like this:
function getPageData(url) {
return got(url);
}
// can only use await inside a function declared as async
async function someFunction() {
try {
let name = await getPageData(url);
console.log(name);
} catch(e) {
console.log(e);
}
}
The got() library does a whole bunch of things for you.
It is entirely based on promises so you can directly use await on the promise it returns.
It collects the whole response for you (you don't have to write your own code to do that).
If the response is JSON, it automatically parses that for you and resolves to the parsed Javascript object.
It automatically detects http or https URL and uses the right low level module.
And, it has dozens of other useful features (not needed in this example).
Or, if you want the lower level solution where you make your own promisified function for doing an https request, you can do this:
const https = require('https');
// can only use await inside a function declared as async
async function someFunction() {
const url = "myurl";
try {
let name = await getPageData(url);
console.log(name);
} catch(e) {
console.log(e);
}
}
function getPageData(url) {
return new Promise((resolve, reject) => {
https.get(url, (resp) => {
let data = '';
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
try {
const name = JSON.parse(data);
resolve(name);
} catch(e) {
// JSON parsing error
reject(e);
}
});
}).on("error", (err) => {
console.log("Error: " + err.message);
reject(err);
});
}).on('error', (err) => {
console.log("Error: " + err.message);
reject(err);
});
}

Related

Getting api using https request function

Well I had a bit problems
function getId(username){
const https = require("https")
let id = ""
let data = ``
https.get(`https://api.roblox.com/users/get-by-username?username=${username}`, (response) =>{
response.on('data', (chunk) => {
data += chunk;
})
response.on('end', () =>{
if(data){
id = JSON.parse(data).Id
}
})
})
.on('error', (error) => {
console.log(error)
})
return id
}
So my goal here to use getId("IHZAQSTORM33") and expected result, it will return user id (1684676332). but instead, it give me (""). It give me a colons
yes I'm trying to connect to roblox api.
Use promise to return the response object as shown in the code.
You can also refer to link for the comment by nkron:
Where is body in a nodejs http.get response?
function getId(username) {
const https = require('https');
let id = '';
let data = '';
const url = `https://api.roblox.com/users/get-by-username?username=${username}`;
return new Promise((resolve, reject) => {
https
.get(url, (response) => {
response.on('data', (chunk) => {
data += chunk;
});
response.on('end', () => {
resolve(data);
});
})
.on('error', reject);
});
//return id
};
(async () => {
const responseObject = await getId('IHZAQSTORM33');
})();
For more on async and await, refer this link:
await is only valid in async function

In NodeJS, how do I await the response of the http2 client library GET call?

I'm using the http2 client package with nodeJS. I want to execute a simple get request, and await the response from the server. So far I have
import * as http2 from "http2";
...
const clientSession = http2.connect(rootDomain);
...
const req = clientSession.request({ ':method': 'GET', ':path': path });
let data = '';
req.on('response', (responseHeaders) => {
// do something with the headers
});
req.on('data', (chunk) => {
data += chunk;
console.log("chunk:" + chunk);
});
req.on('end', () => {
console.log("data:" + data);
console.log("end!");
clientSession.destroy();
});
process.exit(0);
But what I"m not able to figure out is how to do I await the response of the request before exiting? Right now the code flies through to the process.exit line and I can't see a way to block until the request is done.
If you want to await it, then you have to encapsulate it into a function that returns a promise and you can then use await on that promise. Here's one way to do that:
import * as http2 from "http2";
...
function getData(path) {
return new Promise((resolve, reject) => {
const clientSession = http2.connect(rootDomain);
const req = clientSession.request({ ':method': 'GET', ':path': path });
let data = '';
req.on('response', (responseHeaders) => {
// do something with the headers
});
req.on('data', (chunk) => {
data += chunk;
console.log("chunk:" + chunk);
});
req.on('end', () => {
console.log("data:" + data);
console.log("end!");
clientSession.destroy();
resolve(data);
});
req.on('error', (err) => {
clientSession.destroy();
reject(err);
});
});
}
async function run() {
let data = await getData(path);
// do something with data here
}
run().then(() => {
process.exit(0);
}).catch(err => {
console.log(err);
process.exit(1);
});
The other way to do this is to use a higher level http library that does much of this work for you. Here's an example using the got module:
import got from 'got';
async function run() {
let data = await got(url, {http2: true});
// do something with data here
}
In this case, the got() module already supports http2 for you (if you specify that option), already collects the whole response for you and already supports promises (all the things your code needed to add in your original version).
Note, the GET method is the default method which is why it is not necessary to specify it here.
response = await new Promise(async (resolve, reject)=> {
let data = '';
req.on('data', async (chunk) => {
data += chunk;
resolve(JSON.parse(data));
});
});

NodeJS https module create reusable function to send request and handle response dynamically

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.

Nodejs http.get(url[, options][, callback]) method type error while including options

I'm developing a simple chatbot on Amazon Alexa. The idea is to know if an item is on menu at a particular store.
function httpGet() {
return new Promise(((resolve, reject) => {
const options = {
headers: {
'auth_key':'xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx'
}
};
http.get("http://example.com/lookup/api/getitemlist?item=cake&storeid=50", options, (resp) => {
let data = '';
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
resolve(JSON.parse(data));
});
}).on('error', (err) => {
reject(err);
});
}));
}
I'm following documentation on nodejs docs
The API works well in postman where the auth_key is passed in header.
Here's the error from amazon cloudwatch
The API responds with error message when the auth_key isnt present. Am I missing something? From reading the documentation. I thought this would work.
GetItemIntentHandler. I have to write more to handle the response. For now I'm only logging it. This is where I call the function httpGet();
const GetItemIntentHandler = {
canHandle(handlerInput) {
return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
&& Alexa.getIntentName(handlerInput.requestEnvelope) === 'GetItemIntent';
},
async handle(handlerInput){
const item = handlerInput.requestEnvelope.request.intent.slots.Item.value;
const response = await httpGet();
console.log("response reached us");
console.log(response);
return handlerInput.responseBuilder
.speak(` ${item}`)
.reprompt("What would you like?")
.getResponse();
}
};
I used the http.get(options[, callback]) method to get around.
Updated httpGet function I'm using now.
function httpGet() {
return new Promise(((resolve, reject) => {
var options = {
host: 'example.com',
path: '/lookup/api/getitemlist?item=cake&storeid=50',
port: null,
headers: {
'auth_key':'xxxxxxx-xxxxx-xxxxx-xxxxx-xxxxxxxxxx'
}
};
http.get(options, (resp) => {
let data = '';
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
resolve(JSON.parse(data));
});
}).on('error', (err) => {
reject(err);
});
}));
}

Is this a best practice to make API call in Express 4.0

The application is making API request to the server and returning back the response received. Here would like to understand
(i) Is it right way to make API call and receive response from the server.
(ii) if return statement passing back the value is right way
The module is called as
str = apiRequest(search, lang);
And the module is
var https = require('https');
function apiRequest(search, lang) {
var options = {
host: 'localhost:8080',
path: '/api?search=' + search + '&lang=' + lang
};
function resData(res) {
console.log("Status Code : ", res.statusCode);
var str = '';
res.on('data', function (chunk) {
str += chunk;
});
res.on('end', function () {
return JSON.parse(str); // Is this a right way to return data
});
res.on('error', function(e) {
console.error(e);
});
}
https.get(options, resData).end();
};
No, this will not work. I will comment the relevant parts below.
Instead of having this in apiRequest:
function apiRequest(search, lang) {
function resData(res) {
// ...
res.on('end', function () {
return JSON.parse(str); // Is this a right way to return data
});
// ...
}
https.get(options, resData).end();
}
and calling it with:
str = apiRequest(search, lang);
You should either pass a callback or return a promise.
Using callbacks
Your apiRequest function can take an additional argument, a callback:
function apiRequest(search, lang, callback) {
function resData(res) {
// ...
res.on('end', function () {
callback(null, JSON.parse(str));
});
res.on('error', function(e) {
console.error(e);
callback(e);
});
// ...
}
https.get(options, resData).end();
}
Now you can use it as:
apiRequest(search, lang, function (error, str) {
if (err) {
// you have error
} else {
// you have your str here
}
});
Using promises
Your apiRequest function can return a promise:
function apiRequest(search, lang, callback) {
return new Promise(function (resolve, reject) {
function resData(res) {
// ...
res.on('end', function () {
resolve(JSON.parse(str));
});
res.on('error', function(e) {
console.error(e);
reject(e);
});
// ...
}
https.get(options, resData).end();
}
}
Now you can use it as:
apiRequest(search, lang)
.then(function (str) {
// you have your str here
})
.catch(function (err) {
// you have error
});
This is not tested so there might be some minor mistakes but that is the general idea. When I find some errors I'll update the answer.
Summary
To sum it up, there are two styles that you can use to compose asynchronous functions like that: callbacks or promises. You will not be able to just return the data because return is fundamentally synchronous - you have to have something to return right away - unless what you return is a promise that can get resolved or rejected later.
Parsing JSON
What should also keep in mind that you should always run JSON.parse() inside a try {} catch {} block to handle errors of incorrect JSON or otherwise the entire app could crash. JSON.parse() throws exceptions on bad input. See this answer for more info.
This will additionally complicate your code but you can avoid that complication and make it even simpler by using the request module - see updates below.
Simpler examples
Callbacks
To have a working example that is simpler and your don't have to manually parse JSON, consider this code that I just wrote, based on your example but calling a GitHub API so it can be tested by everyone - it prints someone's website given his GitHub nickname but otherwise works similarly to your code:
'use strict';
var request = require('request');
function apiRequest(search, callback) {
var options = {
url: 'https://api.github.com/users/' + search,
json: true,
headers: {'User-Agent': 'request'}
};
function resData(err, res, data) {
if (err) {
callback(err);
} else if (res.statusCode !== 200) {
callback(res.statusCode);
} else {
// data is already parsed as JSON:
callback(null, data.blog);
}
}
request.get(options, resData);
}
apiRequest('rsp', function (err, data) {
if (err) {
console.log('Error:', err);
} else {
console.log('Data:', data);
}
});
This is an example using callbacks.
Promises
And here is an example using promises:
'use strict';
var request = require('request');
function apiRequest(search, callback) {
return new Promise(function (resolve, reject) {
var options = {
url: 'https://api.github.com/users/' + search,
json: true,
headers: {'User-Agent': 'request'}
};
function resData(err, res, data) {
if (err) {
reject(err);
} else if (res.statusCode !== 200) {
reject(res.statusCode);
} else {
// data is already parsed as JSON:
resolve(data.blog);
}
}
request.get(options, resData);
});
}
apiRequest('rsp')
.then(function (data) {
console.log('Data:', data);
})
.catch(function (err) {
console.log('Error:', err);
});
Simplified
It can be simplified even further by using fat arrow functions and anonymous functions and object literals:
'use strict';
var request = require('request');
function apiRequest(search, callback) {
return new Promise((resolve, reject) => {
request.get({
url: 'https://api.github.com/users/' + search,
json: true,
headers: {'User-Agent': 'request'}
}, (err, res, data) => {
if (err) {
reject(err);
} else if (res.statusCode !== 200) {
reject(res.statusCode);
} else {
resolve(data.blog);
}
});
});
}
apiRequest('rsp')
.then(data => console.log('Data:', data))
.catch(err => console.log('Error:', err));
More info
You can see some other answers where I explain the difference between callbacks and promises and how to use the together in more detail, which you may find helpful:
A detailed explanation on how to use callbacks and promises
Explanation on how to use promises in complex request handlers
An explanation of what a promise really is, on the example of AJAX requests
An explanation on how to handle errors in callbacks and promises

Resources